KParts

partloader.cpp
1 /*
2  This file is part of the KDE project
3  SPDX-FileCopyrightText: 2020 David Faure <[email protected]>
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 <KPluginLoader>
14 #include <KService>
15 #include <KSharedConfig>
16 #include <stack>
17 
18 #include <kparts_version.h> // TODO KF6 REMOVE
19 #if KPARTS_VERSION <= QT_VERSION_CHECK(5, 900, 0)
20 #include <KMimeTypeTrader>
21 #include <KPluginInfo>
22 #include <QMimeDatabase>
23 #include <QMimeType>
24 #endif
25 
26 // We still use desktop files for translated descriptions in keditfiletype,
27 // and desktop file names then end up in mimeapps.list.
28 // Alternatively, that KCM could be ported to read the descriptions from the JSON metadata?
29 // KF6 TODO: at least make the KCM write out library names (into a different config file)
30 // so we don't need to do the lookup here every time.
31 static QString pluginForDesktopFile(const QString &desktopFile)
32 {
33  KService::Ptr service = KService::serviceByStorageId(desktopFile);
34  if (!service) {
35  qCDebug(KPARTSLOG) << "mimeapps.list specifies unknown service" << desktopFile;
36  return {};
37  }
38  return service->library();
39 }
40 
41 static QStringList partsFromUserPreference(const QString &mimeType)
42 {
43  auto config = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"));
44  const QStringList desktopFiles = config->group(QStringLiteral("Added KDE Service Associations")).readXdgListEntry(mimeType);
45  QStringList parts;
46  parts.reserve(desktopFiles.count());
47  for (const QString &desktopFile : desktopFiles) {
48  const QString part = pluginForDesktopFile(desktopFile);
49  if (!part.isEmpty()) {
50  parts.append(part);
51  }
52  }
53  return parts;
54 }
55 
56 // A plugin can support N mimetypes. Pick the one that is closest to @parent in the inheritance tree
57 // and return how far it is from that parent (0 = same mimetype, 1 = direct child, etc.)
58 static int pluginDistanceToMimeType(const KPluginMetaData &md, const QString &parent)
59 {
60  QMimeDatabase db;
61  auto distanceToMimeType = [&](const QString &mime) {
62  if (mime == parent) {
63  return 0;
64  }
65  const QStringList ancestors = db.mimeTypeForName(mime).allAncestors();
66  const int dist = ancestors.indexOf(parent);
67  return dist == -1 ? 50 : dist + 1;
68  };
69  const QStringList mimes = md.mimeTypes();
70  int minDistance = 50;
71  for (const QString &mime : mimes) {
72  minDistance = std::min(minDistance, distanceToMimeType(mime));
73  }
74  return minDistance;
75 }
76 
78 {
79  auto supportsMime = [&](const KPluginMetaData &md) {
80  return md.supportsMimeType(mimeType);
81  };
82  QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/parts"), supportsMime);
83 
84 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0)
85  // KF5 compat code
86 
87  // I would compare library filenames, but KPluginMetaData::fileName looks like kf5/kparts/okteta and KService::library() is a full path
88  // The user actually sees translated names, let's ensure those don't look duplicated in the list.
89  auto isPluginForName = [](const QString &name) {
90  return [name](const KPluginMetaData &plugin) {
91  return plugin.name() == name;
92  };
93  };
94 
95  QT_WARNING_PUSH
96  QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
97  QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
98  const KService::List offers = KMimeTypeTrader::self()->query(mimeType, QStringLiteral("KParts/ReadOnlyPart"));
99  for (const KService::Ptr &service : offers) {
100  KPluginInfo info(service);
101  if (info.isValid()) {
102  if (std::find_if(plugins.cbegin(), plugins.cend(), isPluginForName(info.name())) == plugins.cend()) {
103  plugins.append(info.toMetaData());
104  }
105  }
106  }
107  QT_WARNING_POP
108 #endif
109 
110  auto orderPredicate = [&](const KPluginMetaData &left, const KPluginMetaData &right) {
111  // We filtered based on "supports mimetype", but this didn't order from most-specific to least-specific.
112  const int leftDistance = pluginDistanceToMimeType(left, mimeType);
113  const int rightDistance = pluginDistanceToMimeType(right, mimeType);
114  if (leftDistance < rightDistance) {
115  return true;
116  }
117  if (leftDistance > rightDistance) {
118  return false;
119  }
120  // Plugins who support the same mimetype are then sorted by initial preference
121  return left.initialPreference() > right.initialPreference();
122  };
123  std::sort(plugins.begin(), plugins.end(), orderPredicate);
124 
125  const QStringList userParts = partsFromUserPreference(mimeType);
126  if (!userParts.isEmpty()) {
127  // for (const KPluginMetaData &plugin : plugins) {
128  // qDebug() << "unsorted:" << plugin.fileName() << plugin.initialPreference();
129  //}
130  const auto defaultPlugins = plugins;
131  plugins.clear();
132  for (const QString &userPart : userParts) { // e.g. kf5/parts/gvpart
133  auto matchesLibrary = [&](const KPluginMetaData &plugin) {
134  return plugin.fileName().contains(userPart);
135  };
136  auto it = std::find_if(defaultPlugins.begin(), defaultPlugins.end(), matchesLibrary);
137  if (it != defaultPlugins.end()) {
138  plugins.push_back(*it);
139  } else {
140  qCDebug(KPARTSLOG) << "Part not found" << userPart;
141  }
142  }
143  // In case mimeapps.list lists "nothing good", append the default set to the end as fallback
144  plugins += defaultPlugins;
145  }
146 
147  // for (const KPluginMetaData &plugin : plugins) {
148  // qDebug() << plugin.fileName() << plugin.initialPreference();
149  //}
150  return plugins;
151 }
152 
153 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 88)
154 class KPluginFactoryHack : public KPluginFactory
155 {
156 public:
157  QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword) override
158  {
159  return KPluginFactory::create(iface, parentWidget, parent, args, keyword);
160  }
161 };
162 
163 QObject *KParts::PartLoader::Private::createPartInstanceForMimeTypeHelper(const char *iface,
164  const QString &mimeType,
165  QWidget *parentWidget,
166  QObject *parent,
167  QString *error)
168 {
169  QT_WARNING_PUSH
170  QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
171  QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
172  const QVector<KPluginMetaData> plugins = KParts::PartLoader::partsForMimeType(mimeType);
173  for (const KPluginMetaData &plugin : plugins) {
174  KPluginLoader pluginLoader(plugin.fileName());
175  const QString pluginKeyword;
176  KPluginFactory *factory = pluginLoader.factory();
177  if (factory) {
178  QObject *obj = static_cast<KPluginFactoryHack *>(factory)->create(iface, parentWidget, parent, QVariantList(), pluginKeyword);
179  if (error) {
180  if (!obj) {
181  *error = i18n("The plugin '%1' does not provide an interface '%2' with keyword '%3'",
182  plugin.fileName(),
183  QString::fromLatin1(iface),
184  pluginKeyword);
185  } else {
186  error->clear();
187  }
188  }
189  if (obj) {
190  return obj;
191  }
192  } else if (error) {
193  *error = pluginLoader.errorString();
194  }
195  pluginLoader.unload();
196  }
197  if (error) {
198  *error = i18n("No part was found for mimeType %1", mimeType);
199  }
200  return nullptr;
201  QT_WARNING_POP
202 }
203 #endif
void append(const T &value)
T * create(const QString &keyword, QObject *parent=nullptr, const QVariantList &args=QVariantList())
KPARTS_EXPORT QVector< KPluginMetaData > partsForMimeType(const QString &mimeType)
Locate all available KParts for a mimetype.
Definition: partloader.cpp:77
KService::List query(const QString &mimeType, const QString &genericServiceType=QStringLiteral("Application"), const QString &constraint=QString()) const
QVector::iterator begin()
QVector::const_iterator cend() const const
int count(const T &value) const const
void append(const T &value)
void push_back(const T &value)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
void reserve(int alloc)
QAction * create(StandardAction id, const QObject *recvr, const char *slot, QObject *parent)
void clear()
QString i18n(const char *text, const TYPE &arg...)
QStringList mimeTypes() const
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
QVector::const_iterator cbegin() const const
bool isEmpty() const const
bool isEmpty() const const
KSharedConfigPtr config()
int indexOf(QStringView str, int from) const const
bool isValid() const
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
static QVector< KPluginMetaData > findPlugins(const QString &directory, std::function< bool(const KPluginMetaData &)> filter, KPluginMetaDataOption option)
QVector::iterator end()
QString name() const
static Ptr serviceByStorageId(const QString &_storageId)
QString fromLatin1(const char *str, int size)
bool supportsMimeType(const QString &mimeType) const
KPluginMetaData toMetaData() const
The KParts namespace,.
static KMimeTypeTrader * self()
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Tue Aug 9 2022 03:51:33 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.