Warning, file /frameworks/kservice/src/sycoca/kmimeassociations.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2008 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "kmimeassociations_p.h" 0009 #include "sycocadebug.h" 0010 #include <KConfig> 0011 #include <KConfigGroup> 0012 #include <QDebug> 0013 #include <QFile> 0014 #include <QMimeDatabase> 0015 #include <QStandardPaths> 0016 #include <kservice.h> 0017 #include <kservicefactory_p.h> 0018 0019 KMimeAssociations::KMimeAssociations(KOfferHash &offerHash, KServiceFactory *serviceFactory) 0020 : m_offerHash(offerHash) 0021 , m_serviceFactory(serviceFactory) 0022 { 0023 } 0024 0025 /* 0026 0027 The goal of this class is to parse mimeapps.list files, which are used to 0028 let users configure the application-MIME type associations. 0029 0030 Example file: 0031 0032 [Added Associations] 0033 text/plain=gnome-gedit.desktop;gnu-emacs.desktop; 0034 0035 [Removed Associations] 0036 text/plain=gnome-gedit.desktop;gnu-emacs.desktop; 0037 0038 [Default Applications] 0039 text/plain=kate.desktop; 0040 */ 0041 0042 QStringList KMimeAssociations::mimeAppsFiles() 0043 { 0044 QStringList mimeappsFileNames; 0045 // make the list of possible filenames from the spec ($desktop-mimeapps.list, then mimeapps.list) 0046 const QString desktops = QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP")); 0047 const auto list = desktops.split(QLatin1Char(':'), Qt::SkipEmptyParts); 0048 for (const QString &desktop : list) { 0049 mimeappsFileNames.append(desktop.toLower() + QLatin1String("-mimeapps.list")); 0050 } 0051 mimeappsFileNames.append(QStringLiteral("mimeapps.list")); 0052 const QStringList mimeappsDirs = mimeAppsDirs(); 0053 QStringList mimeappsFiles; 0054 // collect existing files 0055 for (const QString &dir : mimeappsDirs) { 0056 for (const QString &file : std::as_const(mimeappsFileNames)) { 0057 const QString filePath = dir + QLatin1Char('/') + file; 0058 if (QFile::exists(filePath) && !mimeappsFiles.contains(filePath)) { 0059 mimeappsFiles.append(filePath); 0060 } 0061 } 0062 } 0063 return mimeappsFiles; 0064 } 0065 0066 QStringList KMimeAssociations::mimeAppsDirs() 0067 { 0068 // list the dirs in the order of the spec (XDG_CONFIG_HOME, XDG_CONFIG_DIRS, XDG_DATA_HOME, XDG_DATA_DIRS) 0069 return QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation) + QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); 0070 } 0071 0072 void KMimeAssociations::parseAllMimeAppsList() 0073 { 0074 int basePreference = 1000; // start high :) 0075 const QStringList files = KMimeAssociations::mimeAppsFiles(); 0076 // Global first, then local 0077 auto it = files.crbegin(); 0078 auto endIt = files.crend(); 0079 for (; it != endIt; ++it) { 0080 // qDebug() << "Parsing" << mimeappsFile; 0081 parseMimeAppsList(*it, basePreference); 0082 basePreference += 50; 0083 } 0084 } 0085 0086 void KMimeAssociations::parseMimeAppsList(const QString &file, int basePreference) 0087 { 0088 KConfig profile(file, KConfig::SimpleConfig); 0089 if (file.endsWith(QLatin1String("/mimeapps.list"))) { // not for $desktop-mimeapps.list 0090 parseAddedAssociations(KConfigGroup(&profile, "Added Associations"), file, basePreference); 0091 parseRemovedAssociations(KConfigGroup(&profile, "Removed Associations"), file); 0092 0093 // KDE extension for parts and plugins, see settings/filetypes/mimetypedata.cpp 0094 parseAddedAssociations(KConfigGroup(&profile, "Added KDE Service Associations"), file, basePreference); 0095 parseRemovedAssociations(KConfigGroup(&profile, "Removed KDE Service Associations"), file); 0096 } 0097 0098 // Default Applications is preferred over Added Associations. 0099 // Other than that, they work the same... 0100 // add 25 to the basePreference to make sure those service offers will have higher preferences 0101 // 25 is arbitrary half of the allocated preference indices for the current parsed mimeapps.list file, defined line 86 0102 parseAddedAssociations(KConfigGroup(&profile, "Default Applications"), file, basePreference + 25); 0103 } 0104 0105 void KMimeAssociations::parseAddedAssociations(const KConfigGroup &group, const QString &file, int basePreference) 0106 { 0107 Q_UNUSED(file) // except in debug statements 0108 QMimeDatabase db; 0109 const auto keyList = group.keyList(); 0110 for (const QString &mimeName : keyList) { 0111 const QStringList services = group.readXdgListEntry(mimeName); 0112 const QString resolvedMimeName = mimeName.startsWith(QLatin1String("x-scheme-handler/")) ? mimeName : db.mimeTypeForName(mimeName).name(); 0113 if (resolvedMimeName.isEmpty()) { 0114 qCDebug(SYCOCA) << file << "specifies unknown MIME type" << mimeName << "in" << group.name(); 0115 } else { 0116 int pref = basePreference; 0117 for (const QString &service : services) { 0118 KService::Ptr pService = m_serviceFactory->findServiceByStorageId(service); 0119 if (!pService) { 0120 qCDebug(SYCOCA) << file << "specifies unknown service" << service << "in" << group.name(); 0121 } else { 0122 // qDebug() << "adding mime" << resolvedMimeName << "to service" << pService->entryPath() << "pref=" << pref; 0123 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 69) 0124 m_offerHash.addServiceOffer(resolvedMimeName, KServiceOffer(pService, pref, 0, pService->allowAsDefault())); 0125 #else 0126 m_offerHash.addServiceOffer(resolvedMimeName, KServiceOffer(pService, pref, 0)); 0127 #endif 0128 --pref; 0129 } 0130 } 0131 } 0132 } 0133 } 0134 0135 void KMimeAssociations::parseRemovedAssociations(const KConfigGroup &group, const QString &file) 0136 { 0137 Q_UNUSED(file) // except in debug statements 0138 const auto keyList = group.keyList(); 0139 for (const QString &mime : keyList) { 0140 const QStringList services = group.readXdgListEntry(mime); 0141 for (const QString &service : services) { 0142 KService::Ptr pService = m_serviceFactory->findServiceByStorageId(service); 0143 if (!pService) { 0144 // qDebug() << file << "specifies unknown service" << service << "in" << group.name(); 0145 } else { 0146 // qDebug() << "removing mime" << mime << "from service" << pService.data() << pService->entryPath(); 0147 m_offerHash.removeServiceOffer(mime, pService); 0148 } 0149 } 0150 } 0151 } 0152 0153 void KOfferHash::addServiceOffer(const QString &serviceType, const KServiceOffer &offer) 0154 { 0155 KService::Ptr service = offer.service(); 0156 // qDebug() << "Adding" << service->entryPath() << "to" << serviceType << offer.preference(); 0157 ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create 0158 QList<KServiceOffer> &offers = data.offers; 0159 QSet<KService::Ptr> &offerSet = data.offerSet; 0160 if (!offerSet.contains(service)) { 0161 offers.append(offer); 0162 offerSet.insert(service); 0163 } else { 0164 const int initPref = offer.preference(); 0165 // qDebug() << service->entryPath() << "already in" << serviceType; 0166 // This happens when mimeapps.list mentions a service (to make it preferred) 0167 // Update initialPreference to std::max(existing offer, new offer) 0168 for (KServiceOffer &servOffer : data.offers) { 0169 if (servOffer.service() == service) { // we can compare KService::Ptrs because they are from the memory hash 0170 servOffer.setPreference(std::max(servOffer.preference(), initPref)); 0171 } 0172 } 0173 } 0174 } 0175 0176 void KOfferHash::removeServiceOffer(const QString &serviceType, const KService::Ptr &service) 0177 { 0178 ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create 0179 data.removedOffers.insert(service); 0180 data.offerSet.remove(service); 0181 0182 const QString id = service->storageId(); 0183 0184 auto &list = data.offers; 0185 auto it = std::remove_if(list.begin(), list.end(), [&id](const KServiceOffer &offer) { 0186 return offer.service()->storageId() == id; 0187 }); 0188 list.erase(it, list.end()); 0189 } 0190 0191 bool KOfferHash::hasRemovedOffer(const QString &serviceType, const KService::Ptr &service) const 0192 { 0193 auto it = m_serviceTypeData.constFind(serviceType); 0194 if (it != m_serviceTypeData.cend()) { 0195 return it.value().removedOffers.contains(service); 0196 } 0197 return false; 0198 }