1 | /* |
2 | This file is part of the KDE project |
3 | SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #include "partloader.h" |
9 | |
10 | #include "kparts_logging.h" |
11 | |
12 | #include <KConfigGroup> |
13 | #include <KLocalizedString> |
14 | #include <KService> |
15 | #include <KSharedConfig> |
16 | |
17 | #include <QJsonArray> |
18 | #include <QMetaEnum> |
19 | #include <QMimeDatabase> |
20 | #include <QMimeType> |
21 | |
22 | static QList<KPluginMetaData> partsFromUserPreference(const QString &mimeType) |
23 | { |
24 | auto config = KSharedConfig::openConfig(QStringLiteral("kpartsrc" ), mode: KConfig::NoGlobals); |
25 | const QStringList pluginIds = config->group(QStringLiteral("Added KDE Part Associations" )).readXdgListEntry(pKey: mimeType); |
26 | QList<KPluginMetaData> plugins; |
27 | plugins.reserve(asize: pluginIds.size()); |
28 | for (const QString &pluginId : pluginIds) { |
29 | if (KPluginMetaData data(QLatin1String("kf6/parts/" ) + pluginId); data.isValid()) { |
30 | plugins << data; |
31 | } |
32 | } |
33 | return plugins; |
34 | } |
35 | |
36 | // A plugin can support N mimetypes. Pick the one that is closest to @parent in the inheritance tree |
37 | // and return how far it is from that parent (0 = same mimetype, 1 = direct child, etc.) |
38 | static int pluginDistanceToMimeType(const KPluginMetaData &md, const QString &parent) |
39 | { |
40 | QMimeDatabase db; |
41 | auto distanceToMimeType = [&](const QString &mime) { |
42 | if (mime == parent) { |
43 | return 0; |
44 | } |
45 | const QStringList ancestors = db.mimeTypeForName(nameOrAlias: mime).allAncestors(); |
46 | const int dist = ancestors.indexOf(str: parent); |
47 | return dist == -1 ? 50 : dist + 1; |
48 | }; |
49 | const QStringList mimes = md.mimeTypes(); |
50 | int minDistance = 50; |
51 | for (const QString &mime : mimes) { |
52 | minDistance = std::min(a: minDistance, b: distanceToMimeType(mime)); |
53 | } |
54 | return minDistance; |
55 | } |
56 | |
57 | KParts::PartCapabilities KParts::PartLoader::partCapabilities(const KPluginMetaData &data) |
58 | { |
59 | QJsonValue capsArrayRaw = data.rawData().value(key: QLatin1String("KParts" )).toObject().value(key: QLatin1String("Capabilities" )); |
60 | KParts::PartCapabilities parsedCapabilties = {}; |
61 | const static QMetaEnum metaEnum = QMetaEnum::fromType<KParts::PartCapability>(); |
62 | QJsonArray capabilities = capsArrayRaw.toArray(); |
63 | for (const QJsonValue &capability : capabilities) { |
64 | bool ok = true; |
65 | PartCapability parsedCapability = (PartCapability)metaEnum.keyToValue(key: capability.toString().toLocal8Bit().constData(), ok: &ok); |
66 | if (ok) { |
67 | parsedCapabilties |= parsedCapability; |
68 | } else { |
69 | qCWarning(KPARTSLOG) << "Could not find capability value" << capability.toString().toLocal8Bit().constData(); |
70 | } |
71 | } |
72 | |
73 | // Don't bother looking at fallback API |
74 | if (!capsArrayRaw.isUndefined()) { |
75 | return parsedCapabilties; |
76 | } |
77 | |
78 | static QMap<QString, KParts::PartCapability> capabilityMapping = { |
79 | {QStringLiteral("KParts/ReadOnlyPart" ), PartCapability::ReadOnly}, |
80 | {QStringLiteral("KParts/ReadWritePart" ), PartCapability::ReadWrite}, |
81 | {QStringLiteral("Browser/View" ), PartCapability::BrowserView}, |
82 | }; |
83 | const auto serviceTypes = data.rawData().value(key: QLatin1String("KPlugin" )).toObject().value(key: QLatin1String("ServiceTypes" )).toVariant().toStringList(); |
84 | if (!serviceTypes.isEmpty()) { |
85 | qCWarning(KPARTSLOG) << data |
86 | << "still defined ServiceTypes - this is deprecated in favor of providing a " |
87 | " \"Capabilities\" list in the \"KParts\" object in the root of the metadata" ; |
88 | for (const QString &serviceType : serviceTypes) { |
89 | auto it = capabilityMapping.find(key: serviceType); |
90 | if (it == capabilityMapping.cend()) { |
91 | qCWarning(KPARTSLOG) << "ServiceType" << serviceType << "from" << data |
92 | << "is not a known value that can be mapped to new Capability enum values" ; |
93 | } else { |
94 | parsedCapabilties |= *it; |
95 | } |
96 | } |
97 | } |
98 | return parsedCapabilties; |
99 | } |
100 | |
101 | QList<KPluginMetaData> KParts::PartLoader::partsForMimeType(const QString &mimeType) |
102 | { |
103 | auto supportsMime = [&mimeType](const KPluginMetaData &md) { |
104 | if (md.supportsMimeType(mimeType)) { |
105 | return true; |
106 | } |
107 | auto pluginJson = md.rawData(); |
108 | auto pluginNamespace = pluginJson.value(key: QLatin1String("KParts" )).toObject().value(key: QLatin1String("PluginNamespace" )).toString(); |
109 | if (pluginNamespace.isEmpty()) { |
110 | return false; |
111 | } |
112 | auto plugins = KPluginMetaData::findPlugins(directory: pluginNamespace, filter: [&mimeType](const KPluginMetaData &pluginMd) { |
113 | return pluginMd.supportsMimeType(mimeType); |
114 | }); |
115 | return !plugins.isEmpty(); |
116 | }; |
117 | QList<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("kf6/parts" ), filter: supportsMime); |
118 | auto orderPredicate = [&](const KPluginMetaData &left, const KPluginMetaData &right) { |
119 | // We filtered based on "supports mimetype", but this didn't order from most-specific to least-specific. |
120 | const int leftDistance = pluginDistanceToMimeType(md: left, parent: mimeType); |
121 | const int rightDistance = pluginDistanceToMimeType(md: right, parent: mimeType); |
122 | if (leftDistance < rightDistance) { |
123 | return true; |
124 | } |
125 | if (leftDistance > rightDistance) { |
126 | return false; |
127 | } |
128 | // Plugins who support the same mimetype are then sorted by initial preference |
129 | const auto getInitialPreference = [](const KPluginMetaData &data) { |
130 | const QJsonObject obj = data.rawData(); |
131 | if (const QJsonValue initialPref = obj.value(key: QLatin1String("KParts" )).toObject().value(key: QLatin1String("InitialPreference" )); |
132 | !initialPref.isUndefined()) { |
133 | return initialPref.toInt(); |
134 | } |
135 | return data.rawData().value(key: QLatin1String("KPlugin" )).toObject().value(key: QLatin1String("InitialPreference" )).toInt(); |
136 | }; |
137 | return getInitialPreference(left) > getInitialPreference(right); |
138 | }; |
139 | std::sort(first: plugins.begin(), last: plugins.end(), comp: orderPredicate); |
140 | |
141 | const QList<KPluginMetaData> userParts = partsFromUserPreference(mimeType); |
142 | if (!userParts.isEmpty()) { |
143 | plugins = userParts; |
144 | } |
145 | return plugins; |
146 | } |
147 | |
148 | void KParts::PartLoader::Private::getErrorStrings(QString *errorString, QString *errorText, const QString &argument, ErrorType type) |
149 | { |
150 | switch (type) { |
151 | case CouldNotLoadPlugin: |
152 | *errorString = i18n("KPluginFactory could not load the plugin: %1" , argument); |
153 | *errorText = QStringLiteral("KPluginFactory could not load the plugin: %1" ).arg(a: argument); |
154 | break; |
155 | case NoPartFoundForMimeType: |
156 | *errorString = i18n("No part was found for mimeType %1" , argument); |
157 | *errorText = QStringLiteral("No part was found for mimeType %1" ).arg(a: argument); |
158 | break; |
159 | case NoPartInstantiatedForMimeType: |
160 | *errorString = i18n("No part could be instantiated for mimeType %1" , argument); |
161 | *errorText = QStringLiteral("No part could be instantiated for mimeType %1" ).arg(a: argument); |
162 | break; |
163 | default: |
164 | qCWarning(KPARTSLOG) << "PartLoader::Private::getErrorStrings got unexpected error type" << type; |
165 | break; |
166 | } |
167 | }; |
168 | |
169 | #include "moc_partloader.cpp" |
170 | |