File indexing completed on 2024-05-19 05:47:23
0001 /* This file is part of the KDE libraries 0002 * Copyright 2008 David Faure <faure@kde.org> 0003 * 0004 * This library is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU Lesser General Public License as published by 0006 * the Free Software Foundation; either version 2 of the License or ( at 0007 * your option ) version 3 or, at the discretion of KDE e.V. ( which shall 0008 * act as a proxy as in section 14 of the GPLv3 ), any later version. 0009 * 0010 * This library is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 * Library General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU Lesser General Public License 0016 * along with this library; see the file COPYING.LIB. If not, write to 0017 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 * Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "kmimeassociations_p.h" 0022 #include <kservice.h> 0023 #include <kservicefactory_p.h> 0024 #include <kconfiggroup.h> 0025 #include <kconfig.h> 0026 #include <QDebug> 0027 #include <QFile> 0028 #include <qstandardpaths.h> 0029 #include <qmimedatabase.h> 0030 #include "sycocadebug.h" 0031 0032 KMimeAssociations::KMimeAssociations(KOfferHash &offerHash, KServiceFactory *serviceFactory) 0033 : m_offerHash(offerHash), m_serviceFactory(serviceFactory) 0034 { 0035 } 0036 0037 /* 0038 0039 The goal of this class is to parse mimeapps.list files, which are used to 0040 let users configure the application-mimetype associations. 0041 0042 Example file: 0043 0044 [Added Associations] 0045 text/plain=kate.desktop; 0046 0047 [Removed Associations] 0048 text/plain=gnome-gedit.desktop;gnu-emacs.desktop; 0049 0050 */ 0051 0052 void KMimeAssociations::parseAllMimeAppsList() 0053 { 0054 QStringList mimeappsFileNames; 0055 // make the list of possible filenames from the spec ($desktop-mimeapps.list, then mimeapps.list) 0056 const QString desktops = QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP")); 0057 const auto list = desktops.split(QLatin1Char(':'), QString::SkipEmptyParts); 0058 for (const QString &desktop : list) { 0059 mimeappsFileNames.append(desktop.toLower() + QLatin1String("-mimeapps.list")); 0060 } 0061 mimeappsFileNames.append(QStringLiteral("mimeapps.list")); 0062 // list the dirs in the order of the spec (XDG_CONFIG_HOME, XDG_CONFIG_DIRS, XDG_DATA_HOME, XDG_DATA_DIRS) 0063 const QStringList mimeappsDirs = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation) 0064 + QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); 0065 QStringList mimeappsFiles; 0066 // collect existing files 0067 for (const QString &dir : mimeappsDirs) { 0068 for (const QString &file : qAsConst(mimeappsFileNames)) { 0069 const QString filePath = dir + QLatin1Char('/') + file; 0070 if (QFile::exists(filePath)) { 0071 mimeappsFiles.append(filePath); 0072 } 0073 } 0074 } 0075 //qDebug() << "FILE LIST:" << mimeappsFiles; 0076 0077 int basePreference = 1000; // start high :) 0078 QListIterator<QString> mimeappsIter(mimeappsFiles); 0079 mimeappsIter.toBack(); 0080 while (mimeappsIter.hasPrevious()) { // global first, then local. 0081 const QString mimeappsFile = mimeappsIter.previous(); 0082 //qDebug() << "Parsing" << mimeappsFile; 0083 parseMimeAppsList(mimeappsFile, basePreference); 0084 basePreference += 50; 0085 } 0086 } 0087 0088 void KMimeAssociations::parseMimeAppsList(const QString &file, int basePreference) 0089 { 0090 KConfig profile(file, KConfig::SimpleConfig); 0091 if (file.endsWith(QLatin1String("/mimeapps.list"))) { // not for $desktop-mimeapps.list 0092 parseAddedAssociations(KConfigGroup(&profile, "Added Associations"), file, basePreference); 0093 parseRemovedAssociations(KConfigGroup(&profile, "Removed Associations"), file); 0094 0095 // KDE extension for parts and plugins, see settings/filetypes/mimetypedata.cpp 0096 parseAddedAssociations(KConfigGroup(&profile, "Added KDE Service Associations"), file, basePreference); 0097 parseRemovedAssociations(KConfigGroup(&profile, "Removed KDE Service Associations"), file); 0098 } 0099 0100 // TODO "Default Applications" is a separate query and a separate algorithm, says the spec. 0101 // For now this is better than nothing though. 0102 parseAddedAssociations(KConfigGroup(&profile, "Default Applications"), file, basePreference); 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 mimeType" << 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 m_offerHash.addServiceOffer(resolvedMimeName, KServiceOffer(pService, pref, 0, pService->allowAsDefault())); 0124 --pref; 0125 } 0126 } 0127 } 0128 } 0129 } 0130 0131 void KMimeAssociations::parseRemovedAssociations(const KConfigGroup &group, const QString &file) 0132 { 0133 Q_UNUSED(file) // except in debug statements 0134 const auto keyList = group.keyList(); 0135 for (const QString &mime : keyList) { 0136 const QStringList services = group.readXdgListEntry(mime); 0137 for (const QString &service : services) { 0138 KService::Ptr pService = m_serviceFactory->findServiceByStorageId(service); 0139 if (!pService) { 0140 //qDebug() << file << "specifies unknown service" << service << "in" << group.name(); 0141 } else { 0142 //qDebug() << "removing mime" << mime << "from service" << pService.data() << pService->entryPath(); 0143 m_offerHash.removeServiceOffer(mime, pService); 0144 } 0145 } 0146 } 0147 } 0148 0149 void KOfferHash::addServiceOffer(const QString &serviceType, const KServiceOffer &offer) 0150 { 0151 KService::Ptr service = offer.service(); 0152 //qDebug() << "Adding" << service->entryPath() << "to" << serviceType << offer.preference(); 0153 ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create 0154 QList<KServiceOffer> &offers = data.offers; 0155 QSet<KService::Ptr> &offerSet = data.offerSet; 0156 if (!offerSet.contains(service)) { 0157 offers.append(offer); 0158 offerSet.insert(service); 0159 } else { 0160 //qDebug() << service->entryPath() << "already in" << serviceType; 0161 // This happens when mimeapps.list mentions a service (to make it preferred) 0162 // Update initialPreference to qMax(existing offer, new offer) 0163 QMutableListIterator<KServiceOffer> sfit(data.offers); 0164 while (sfit.hasNext()) { 0165 if (sfit.next().service() == service) { // we can compare KService::Ptrs because they are from the memory hash 0166 sfit.value().setPreference(qMax(sfit.value().preference(), offer.preference())); 0167 } 0168 } 0169 } 0170 } 0171 0172 void KOfferHash::removeServiceOffer(const QString &serviceType, const KService::Ptr &service) 0173 { 0174 ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create 0175 data.removedOffers.insert(service); 0176 data.offerSet.remove(service); 0177 QMutableListIterator<KServiceOffer> sfit(data.offers); 0178 while (sfit.hasNext()) { 0179 if (sfit.next().service()->storageId() == service->storageId()) { 0180 sfit.remove(); 0181 } 0182 } 0183 } 0184 0185 bool KOfferHash::hasRemovedOffer(const QString &serviceType, const KService::Ptr &service) const 0186 { 0187 QHash<QString, ServiceTypeOffersData>::const_iterator it = m_serviceTypeData.find(serviceType); 0188 if (it != m_serviceTypeData.end()) { 0189 return (*it).removedOffers.contains(service); 0190 } 0191 return false; 0192 }