| 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 | |