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
22static 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.)
38static 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
57KParts::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
101QList<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
148void 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

source code of kparts/src/partloader.cpp