File indexing completed on 2024-04-28 15:29:22
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "partloader.h" 0009 0010 #include "kparts_logging.h" 0011 0012 #include <KConfigGroup> 0013 #include <KLocalizedString> 0014 #include <KPluginLoader> 0015 #include <KService> 0016 #include <KSharedConfig> 0017 #include <stack> 0018 0019 #include <kparts_version.h> // TODO KF6 REMOVE 0020 #if KPARTS_VERSION <= QT_VERSION_CHECK(5, 900, 0) 0021 #include <KMimeTypeTrader> 0022 #include <KPluginInfo> 0023 #include <QMimeDatabase> 0024 #include <QMimeType> 0025 #endif 0026 0027 // We still use desktop files for translated descriptions in keditfiletype, 0028 // and desktop file names then end up in mimeapps.list. 0029 // Alternatively, that KCM could be ported to read the descriptions from the JSON metadata? 0030 // KF6 TODO: at least make the KCM write out library names (into a different config file) 0031 // so we don't need to do the lookup here every time. 0032 static QString pluginForDesktopFile(const QString &desktopFile) 0033 { 0034 KService::Ptr service = KService::serviceByStorageId(desktopFile); 0035 if (!service) { 0036 qCDebug(KPARTSLOG) << "mimeapps.list specifies unknown service" << desktopFile; 0037 return {}; 0038 } 0039 return service->library(); 0040 } 0041 0042 static QStringList partsFromUserPreference(const QString &mimeType) 0043 { 0044 auto config = KSharedConfig::openConfig(QStringLiteral("mimeapps.list")); 0045 const QStringList desktopFiles = config->group(QStringLiteral("Added KDE Service Associations")).readXdgListEntry(mimeType); 0046 QStringList parts; 0047 parts.reserve(desktopFiles.count()); 0048 for (const QString &desktopFile : desktopFiles) { 0049 const QString part = pluginForDesktopFile(desktopFile); 0050 if (!part.isEmpty()) { 0051 parts.append(part); 0052 } 0053 } 0054 return parts; 0055 } 0056 0057 // A plugin can support N mimetypes. Pick the one that is closest to @parent in the inheritance tree 0058 // and return how far it is from that parent (0 = same mimetype, 1 = direct child, etc.) 0059 static int pluginDistanceToMimeType(const KPluginMetaData &md, const QString &parent) 0060 { 0061 QMimeDatabase db; 0062 auto distanceToMimeType = [&](const QString &mime) { 0063 if (mime == parent) { 0064 return 0; 0065 } 0066 const QStringList ancestors = db.mimeTypeForName(mime).allAncestors(); 0067 const int dist = ancestors.indexOf(parent); 0068 return dist == -1 ? 50 : dist + 1; 0069 }; 0070 const QStringList mimes = md.mimeTypes(); 0071 int minDistance = 50; 0072 for (const QString &mime : mimes) { 0073 minDistance = std::min(minDistance, distanceToMimeType(mime)); 0074 } 0075 return minDistance; 0076 } 0077 0078 QVector<KPluginMetaData> KParts::PartLoader::partsForMimeType(const QString &mimeType) 0079 { 0080 auto supportsMime = [&](const KPluginMetaData &md) { 0081 return md.supportsMimeType(mimeType); 0082 }; 0083 QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/parts"), supportsMime); 0084 0085 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0) 0086 // KF5 compat code 0087 0088 // I would compare library filenames, but KPluginMetaData::fileName looks like kf5/kparts/okteta and KService::library() is a full path 0089 // The user actually sees translated names, let's ensure those don't look duplicated in the list. 0090 auto isPluginForName = [](const QString &name) { 0091 return [name](const KPluginMetaData &plugin) { 0092 return plugin.name() == name; 0093 }; 0094 }; 0095 0096 QT_WARNING_PUSH 0097 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") 0098 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") 0099 const KService::List offers = KMimeTypeTrader::self()->query(mimeType, QStringLiteral("KParts/ReadOnlyPart")); 0100 for (const KService::Ptr &service : offers) { 0101 KPluginInfo info(service); 0102 if (info.isValid()) { 0103 if (std::find_if(plugins.cbegin(), plugins.cend(), isPluginForName(info.name())) == plugins.cend()) { 0104 plugins.append(info.toMetaData()); 0105 } 0106 } 0107 } 0108 QT_WARNING_POP 0109 #endif 0110 0111 auto orderPredicate = [&](const KPluginMetaData &left, const KPluginMetaData &right) { 0112 // We filtered based on "supports mimetype", but this didn't order from most-specific to least-specific. 0113 const int leftDistance = pluginDistanceToMimeType(left, mimeType); 0114 const int rightDistance = pluginDistanceToMimeType(right, mimeType); 0115 if (leftDistance < rightDistance) { 0116 return true; 0117 } 0118 if (leftDistance > rightDistance) { 0119 return false; 0120 } 0121 // Plugins who support the same mimetype are then sorted by initial preference 0122 return left.initialPreference() > right.initialPreference(); 0123 }; 0124 std::sort(plugins.begin(), plugins.end(), orderPredicate); 0125 0126 const QStringList userParts = partsFromUserPreference(mimeType); 0127 if (!userParts.isEmpty()) { 0128 // for (const KPluginMetaData &plugin : plugins) { 0129 // qDebug() << "unsorted:" << plugin.fileName() << plugin.initialPreference(); 0130 //} 0131 const auto defaultPlugins = plugins; 0132 plugins.clear(); 0133 for (const QString &userPart : userParts) { // e.g. kf5/parts/gvpart 0134 auto matchesLibrary = [&](const KPluginMetaData &plugin) { 0135 return plugin.fileName().contains(userPart); 0136 }; 0137 auto it = std::find_if(defaultPlugins.begin(), defaultPlugins.end(), matchesLibrary); 0138 if (it != defaultPlugins.end()) { 0139 plugins.push_back(*it); 0140 } else { 0141 qCDebug(KPARTSLOG) << "Part not found" << userPart; 0142 } 0143 } 0144 // In case mimeapps.list lists "nothing good", append the default set to the end as fallback 0145 plugins += defaultPlugins; 0146 } 0147 0148 // for (const KPluginMetaData &plugin : plugins) { 0149 // qDebug() << plugin.fileName() << plugin.initialPreference(); 0150 //} 0151 return plugins; 0152 } 0153 0154 void KParts::PartLoader::Private::getErrorStrings(QString *errorString, QString *errorText, const QString &argument, ErrorType type) 0155 { 0156 switch (type) { 0157 case CouldNotLoadPlugin: 0158 *errorString = i18n("KPluginFactory could not load the plugin: %1", argument); 0159 *errorText = QStringLiteral("KPluginFactory could not load the plugin: %1").arg(argument); 0160 break; 0161 case NoPartFoundForMimeType: 0162 *errorString = i18n("No part was found for mimeType %1", argument); 0163 *errorText = QStringLiteral("No part was found for mimeType %1").arg(argument); 0164 break; 0165 case NoPartInstantiatedForMimeType: 0166 *errorString = i18n("No part could be instantiated for mimeType %1", argument); 0167 *errorText = QStringLiteral("No part could be instantiated for mimeType %1").arg(argument); 0168 break; 0169 default: 0170 qCWarning(KPARTSLOG) << "PartLoader::Private::getErrorStrings got unexpected error type" << type; 0171 break; 0172 } 0173 }; 0174 0175 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 88) 0176 class KPluginFactoryHack : public KPluginFactory 0177 { 0178 public: 0179 QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword) override 0180 { 0181 return KPluginFactory::create(iface, parentWidget, parent, args, keyword); 0182 } 0183 }; 0184 0185 QObject *KParts::PartLoader::Private::createPartInstanceForMimeTypeHelper(const char *iface, 0186 const QString &mimeType, 0187 QWidget *parentWidget, 0188 QObject *parent, 0189 QString *error) 0190 { 0191 QT_WARNING_PUSH 0192 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") 0193 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") 0194 const QVector<KPluginMetaData> plugins = KParts::PartLoader::partsForMimeType(mimeType); 0195 for (const KPluginMetaData &plugin : plugins) { 0196 KPluginLoader pluginLoader(plugin.fileName()); 0197 const QString pluginKeyword; 0198 KPluginFactory *factory = pluginLoader.factory(); 0199 if (factory) { 0200 QObject *obj = static_cast<KPluginFactoryHack *>(factory)->create(iface, parentWidget, parent, QVariantList(), pluginKeyword); 0201 if (error) { 0202 if (!obj) { 0203 *error = i18n("The plugin '%1' does not provide an interface '%2' with keyword '%3'", 0204 plugin.fileName(), 0205 QString::fromLatin1(iface), 0206 pluginKeyword); 0207 } else { 0208 error->clear(); 0209 } 0210 } 0211 if (obj) { 0212 return obj; 0213 } 0214 } else if (error) { 0215 *error = pluginLoader.errorString(); 0216 } 0217 pluginLoader.unload(); 0218 } 0219 if (error) { 0220 *error = i18n("No part was found for mimeType %1", mimeType); 0221 } 0222 return nullptr; 0223 QT_WARNING_POP 0224 } 0225 #endif