KService

kmimeassociations.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2008 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "kmimeassociations_p.h"
9#include "sycocadebug.h"
10#include <KConfig>
11#include <KConfigGroup>
12#include <QDebug>
13#include <QFile>
14#include <QFileInfo>
15#include <QMimeDatabase>
16#include <QStandardPaths>
17#include <kservice.h>
18#include <kservicefactory_p.h>
19
20KMimeAssociations::KMimeAssociations(KOfferHash &offerHash, KServiceFactory *serviceFactory)
21 : m_offerHash(offerHash)
22 , m_serviceFactory(serviceFactory)
23{
24}
25
26/*
27
28The goal of this class is to parse mimeapps.list files, which are used to
29let users configure the application-MIME type associations.
30
31Example file:
32
33[Added Associations]
34text/plain=gnome-gedit.desktop;gnu-emacs.desktop;
35
36[Removed Associations]
37text/plain=gnome-gedit.desktop;gnu-emacs.desktop;
38
39[Default Applications]
40text/plain=kate.desktop;
41*/
42
43QStringList KMimeAssociations::mimeAppsFiles()
44{
45 QStringList mimeappsFileNames;
46 // make the list of possible filenames from the spec ($desktop-mimeapps.list, then mimeapps.list)
47 const QString desktops = QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP"));
48 const auto list = desktops.split(QLatin1Char(':'), Qt::SkipEmptyParts);
49 for (const QString &desktop : list) {
50 mimeappsFileNames.append(desktop.toLower() + QLatin1String("-mimeapps.list"));
51 }
52 mimeappsFileNames.append(QStringLiteral("mimeapps.list"));
53 const QStringList mimeappsDirs = mimeAppsDirs();
54 QStringList mimeappsFiles;
55 // collect existing files
56 for (const QString &dir : mimeappsDirs) {
57 for (const QString &file : std::as_const(mimeappsFileNames)) {
58 const QFileInfo fileInfo(dir + QLatin1Char('/') + file);
59 const QString filePath = fileInfo.canonicalFilePath();
60 if (!filePath.isEmpty() && !mimeappsFiles.contains(filePath)) {
61 mimeappsFiles.append(filePath);
62 }
63 }
64 }
65 return mimeappsFiles;
66}
67
68QStringList KMimeAssociations::mimeAppsDirs()
69{
70 // list the dirs in the order of the spec (XDG_CONFIG_HOME, XDG_CONFIG_DIRS, XDG_DATA_HOME, XDG_DATA_DIRS)
72}
73
74void KMimeAssociations::parseAllMimeAppsList()
75{
76 int basePreference = 1000; // start high :)
77 const QStringList files = KMimeAssociations::mimeAppsFiles();
78 // Global first, then local
79 auto it = files.crbegin();
80 auto endIt = files.crend();
81 for (; it != endIt; ++it) {
82 // qDebug() << "Parsing" << mimeappsFile;
83 parseMimeAppsList(*it, basePreference);
84 basePreference += 50;
85 }
86}
87
88void KMimeAssociations::parseMimeAppsList(const QString &file, int basePreference)
89{
90 KConfig profile(file, KConfig::SimpleConfig);
91 if (file.endsWith(QLatin1String("/mimeapps.list"))) { // not for $desktop-mimeapps.list
92 parseAddedAssociations(KConfigGroup(&profile, QStringLiteral("Added Associations")), file, basePreference);
93 parseRemovedAssociations(KConfigGroup(&profile, QStringLiteral("Removed Associations")), file);
94
95 // KDE extension for parts and plugins, see settings/filetypes/mimetypedata.cpp
96 parseAddedAssociations(KConfigGroup(&profile, QStringLiteral("Added KDE Service Associations")), file, basePreference);
97 parseRemovedAssociations(KConfigGroup(&profile, QStringLiteral("Removed KDE Service Associations")), file);
98 }
99
100 // Default Applications is preferred over Added Associations.
101 // Other than that, they work the same...
102 // add 25 to the basePreference to make sure those service offers will have higher preferences
103 // 25 is arbitrary half of the allocated preference indices for the current parsed mimeapps.list file, defined line 86
104 parseAddedAssociations(KConfigGroup(&profile, QStringLiteral("Default Applications")), file, basePreference + 25);
105}
106
107void KMimeAssociations::parseAddedAssociations(const KConfigGroup &group, const QString &file, int basePreference)
108{
109 Q_UNUSED(file) // except in debug statements
110 QMimeDatabase db;
111 const auto keyList = group.keyList();
112 for (const QString &mimeName : keyList) {
113 const QStringList services = group.readXdgListEntry(mimeName);
114 const QString resolvedMimeName = mimeName.startsWith(QLatin1String("x-scheme-handler/")) ? mimeName : db.mimeTypeForName(mimeName).name();
115 if (resolvedMimeName.isEmpty()) {
116 qCDebug(SYCOCA) << file << "specifies unknown MIME type" << mimeName << "in" << group.name();
117 } else {
118 int pref = basePreference;
119 for (const QString &service : services) {
120 KService::Ptr pService = m_serviceFactory->findServiceByStorageId(service);
121 if (!pService) {
122 qCDebug(SYCOCA) << file << "specifies unknown service" << service << "in" << group.name();
123 } else {
124 // qDebug() << "adding mime" << resolvedMimeName << "to service" << pService->entryPath() << "pref=" << pref;
125 m_offerHash.addServiceOffer(resolvedMimeName, KServiceOffer(pService, pref, 0));
126 --pref;
127 }
128 }
129 }
130 }
131}
132
133void KMimeAssociations::parseRemovedAssociations(const KConfigGroup &group, const QString &file)
134{
135 Q_UNUSED(file) // except in debug statements
136 const auto keyList = group.keyList();
137 for (const QString &mime : keyList) {
138 const QStringList services = group.readXdgListEntry(mime);
139 for (const QString &service : services) {
140 KService::Ptr pService = m_serviceFactory->findServiceByStorageId(service);
141 if (!pService) {
142 // qDebug() << file << "specifies unknown service" << service << "in" << group.name();
143 } else {
144 // qDebug() << "removing mime" << mime << "from service" << pService.data() << pService->entryPath();
145 m_offerHash.removeServiceOffer(mime, pService);
146 }
147 }
148 }
149}
150
151void KOfferHash::addServiceOffer(const QString &serviceType, const KServiceOffer &offer)
152{
153 KService::Ptr service = offer.service();
154 // qDebug() << "Adding" << service->entryPath() << "to" << serviceType << offer.preference();
155 ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create
156 QList<KServiceOffer> &offers = data.offers;
157 QSet<KService::Ptr> &offerSet = data.offerSet;
158 if (!offerSet.contains(service)) {
159 offers.append(offer);
160 offerSet.insert(service);
161 } else {
162 const int initPref = offer.preference();
163 // qDebug() << service->entryPath() << "already in" << serviceType;
164 // This happens when mimeapps.list mentions a service (to make it preferred)
165 // Update initialPreference to std::max(existing offer, new offer)
166 for (KServiceOffer &servOffer : data.offers) {
167 if (servOffer.service() == service) { // we can compare KService::Ptrs because they are from the memory hash
168 servOffer.setPreference(std::max(servOffer.preference(), initPref));
169 }
170 }
171 }
172}
173
174void KOfferHash::removeServiceOffer(const QString &serviceType, const KService::Ptr &service)
175{
176 ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create
177 data.removedOffers.insert(service);
178 data.offerSet.remove(service);
179
180 const QString id = service->storageId();
181
182 auto &list = data.offers;
183 auto it = std::remove_if(list.begin(), list.end(), [&id](const KServiceOffer &offer) {
184 return offer.service()->storageId() == id;
185 });
186 list.erase(it, list.end());
187}
188
189bool KOfferHash::hasRemovedOffer(const QString &serviceType, const KService::Ptr &service) const
190{
191 auto it = m_serviceTypeData.constFind(serviceType);
192 if (it != m_serviceTypeData.cend()) {
193 return it.value().removedOffers.contains(service);
194 }
195 return false;
196}
QString name() const
QStringList readXdgListEntry(const char *key, const QStringList &aDefault=QStringList()) const
QStringList keyList() const
Holds the user's preference of a service.
void setPreference(int p)
The bigger this number is, the better is this service.
int preference() const
The bigger this number is, the better is this service.
KService::Ptr service() const
The service which this offer is about.
KIOCORE_EXPORT QStringList list(const QString &fileClass)
void append(QList< T > &&value)
iterator begin()
const_reverse_iterator crbegin() const const
const_reverse_iterator crend() const const
iterator end()
iterator erase(const_iterator begin, const_iterator end)
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
QStringList standardLocations(StandardLocation type)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
SkipEmptyParts
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:00:21 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.