File indexing completed on 2024-04-28 15:29:52

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999 Torben Weis <weis@kde.org>
0004     SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #include "kservicetypeprofile.h"
0010 #include "kservicetypeprofile_p.h"
0011 
0012 #include "kservice.h"
0013 #include "kserviceoffer.h"
0014 #include "kservicetype.h"
0015 #include "ksycoca_p.h"
0016 
0017 #include <KConfig>
0018 #include <KConfigGroup>
0019 
0020 #include <QHash>
0021 #include <QMutex>
0022 #include <QtAlgorithms>
0023 
0024 // servicetype -> profile
0025 class KServiceTypeProfiles : public QHash<QString, KServiceTypeProfileEntry *>
0026 {
0027 public:
0028     KServiceTypeProfiles()
0029     {
0030         m_parsed = false;
0031         ensureParsed();
0032     }
0033     ~KServiceTypeProfiles()
0034     {
0035         clear();
0036     }
0037     void clear()
0038     {
0039         QMutexLocker lock(&m_mutex);
0040         qDeleteAll(*this);
0041         QHash<QString, KServiceTypeProfileEntry *>::clear();
0042         m_parsed = false;
0043     }
0044     bool hasProfile(const QString &serviceType)
0045     {
0046         QMutexLocker lock(&m_mutex);
0047         ensureParsed();
0048         return contains(serviceType);
0049     }
0050     void ensureParsed(); // mutex must be locked when calling this
0051     QMutex m_mutex;
0052 
0053 private:
0054     bool m_parsed;
0055 };
0056 
0057 Q_GLOBAL_STATIC(KServiceTypeProfiles, s_serviceTypeProfiles)
0058 
0059 void KServiceTypeProfiles::ensureParsed()
0060 {
0061     if (m_parsed) {
0062         return;
0063     }
0064     m_parsed = true;
0065 
0066     // Read the service type profiles from servicetype_profilerc
0067     // See writeServiceTypeProfile for a description of the file format.
0068     // ### Since this new format names groups after servicetypes maybe we can even
0069     // avoid doing any init upfront, and just look up the group when asked...
0070     KConfig configFile(QStringLiteral("servicetype_profilerc"), KConfig::NoGlobals);
0071     const QStringList tmpList = configFile.groupList();
0072     for (const auto &type : tmpList) {
0073         KConfigGroup config(&configFile, type);
0074         const int count = config.readEntry("NumberOfEntries", 0);
0075         KServiceTypeProfileEntry *p = this->value(type, nullptr);
0076         if (!p) {
0077             p = new KServiceTypeProfileEntry();
0078             this->insert(type, p);
0079         }
0080 
0081         for (int i = 0; i < count; ++i) {
0082             const QString num = QLatin1String("Entry") + QString::number(i);
0083             const QString serviceId = config.readEntry(num + QLatin1String("_Service"), QString());
0084             if (!serviceId.isEmpty()) {
0085                 const int pref = config.readEntry(num + QLatin1String("_Preference"), 0);
0086                 // qDebug() << "adding service " << serviceId << " to profile for " << type << " with preference " << pref;
0087                 p->addService(serviceId, pref);
0088             }
0089         }
0090     }
0091 }
0092 
0093 // static
0094 void KServiceTypeProfile::clearCache()
0095 {
0096     if (s_serviceTypeProfiles.exists()) {
0097         s_serviceTypeProfiles()->clear();
0098     }
0099 }
0100 
0101 /**
0102  * Returns the offers in the profile for the requested service type.
0103  * @param list list of offers (including initialPreference)
0104  * @param servicetype the service type
0105  * @return the weighted and sorted offer list
0106  * @internal used by KServiceTypeTrader
0107  */
0108 namespace KServiceTypeProfile
0109 {
0110 KServiceOfferList sortServiceTypeOffers(const KServiceOfferList &list, const QString &servicetype);
0111 }
0112 
0113 KServiceOfferList KServiceTypeProfile::sortServiceTypeOffers(const KServiceOfferList &list, const QString &serviceType)
0114 {
0115     QMutexLocker lock(&s_serviceTypeProfiles()->m_mutex);
0116     s_serviceTypeProfiles()->ensureParsed();
0117     KServiceTypeProfileEntry *profile = s_serviceTypeProfiles()->value(serviceType, nullptr);
0118 
0119     KServiceOfferList offers;
0120 
0121     for (const auto &offer : list) {
0122         const KService::Ptr servPtr = offer.service();
0123         // qDebug() << "KServiceTypeProfile::offers considering " << servPtr->storageId();
0124         // Look into the profile (if there's one), to find this service's preference.
0125         bool foundInProfile = false;
0126         if (profile) {
0127             auto it2 = profile->m_mapServices.constFind(servPtr->storageId());
0128             if (it2 != profile->m_mapServices.constEnd()) {
0129                 const int pref = it2.value();
0130                 // qDebug() << "found in mapServices pref=" << pref;
0131                 if (pref > 0) { // 0 disables the service
0132 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 69)
0133                     offers.append(KServiceOffer(servPtr, pref, 0, servPtr->allowAsDefault()));
0134 #else
0135                     offers.append(KServiceOffer(servPtr, pref, 0));
0136 #endif
0137                 }
0138                 foundInProfile = true;
0139             }
0140         }
0141         if (!foundInProfile) {
0142             // This offer isn't in the profile
0143             // This can be because we have no profile at all, or because the
0144             // services have been installed after the profile was written,
0145             // but it's also the case for any service that's neither App nor ReadOnlyPart, e.g. RenameDlg/Plugin
0146             // qDebug() << "not found in mapServices. Appending.";
0147 
0148             // If there's a profile, we use 0 as the preference to ensure new apps don't take over existing apps (which default to 1)
0149             offers.append(KServiceOffer(servPtr,
0150                                         profile ? 0 : offer.preference(),
0151 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 69)
0152                                         0,
0153                                         servPtr->allowAsDefault()));
0154 #else
0155                                         0));
0156 #endif
0157         }
0158     }
0159 
0160     std::stable_sort(offers.begin(), offers.end());
0161 
0162     // qDebug() << "KServiceTypeProfile::offers returning " << offers.count() << " offers";
0163     return offers;
0164 }
0165 
0166 bool KServiceTypeProfile::hasProfile(const QString &serviceType)
0167 {
0168     return s_serviceTypeProfiles()->hasProfile(serviceType);
0169 }
0170 
0171 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 66)
0172 void KServiceTypeProfile::writeServiceTypeProfile(const QString &serviceType, const KService::List &services, const KService::List &disabledServices)
0173 {
0174     /*
0175      * [ServiceType]
0176      * NumEntries=3
0177      * Entry0_Service=serv.desktop
0178      * Entry0_Preference=10
0179      * Entry1_Service=otherserv.desktop
0180      * Entry1_Preference=5
0181      * Entry2_Service=broken_service.desktop
0182      * Entry2_Preference=0
0183      */
0184 
0185     KConfig configFile(QStringLiteral("servicetype_profilerc"), KConfig::SimpleConfig);
0186     configFile.deleteGroup(serviceType);
0187 
0188     KConfigGroup config(&configFile, serviceType);
0189     const int count = services.count();
0190     config.writeEntry("NumberOfEntries", count + disabledServices.count());
0191     KService::List::ConstIterator servit = services.begin();
0192     int i = 0;
0193     for (; servit != services.end(); ++servit, ++i) {
0194         if (*servit) {
0195             const QString num = QLatin1String("Entry") + QString::number(i);
0196             config.writeEntry(num + QLatin1String("_Service"), (*servit)->storageId());
0197             config.writeEntry(num + QLatin1String("_Preference"), count - i);
0198         }
0199     }
0200     servit = disabledServices.begin();
0201     for (; servit != disabledServices.end(); ++servit, ++i) {
0202         if (*servit) {
0203             const QString num = QLatin1String("Entry") + QString::number(i);
0204             config.writeEntry(num + QLatin1String("_Service"), (*servit)->storageId());
0205             config.writeEntry(num + QLatin1String("_Preference"), 0);
0206         }
0207     }
0208     configFile.sync();
0209 
0210     // Drop the whole cache...
0211     clearCache();
0212 }
0213 #endif
0214 
0215 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 66)
0216 void KServiceTypeProfile::deleteServiceTypeProfile(const QString &serviceType)
0217 {
0218     KConfig config(QStringLiteral("servicetype_profilerc"), KConfig::SimpleConfig);
0219     config.deleteGroup(serviceType);
0220     config.sync();
0221 
0222     // Not threadsafe, but well the whole idea of using this method isn't
0223     // threadsafe in the first place.
0224     if (s_serviceTypeProfiles.exists()) {
0225         delete s_serviceTypeProfiles()->take(serviceType);
0226     }
0227 }
0228 #endif