Akonadi

typepluginloader.cpp
1 /*
2  SPDX-FileCopyrightText: 2007 Till Adam <[email protected]>
3  SPDX-FileCopyrightText: 2007 Volker Krause <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "typepluginloader_p.h"
9 
10 #include "item.h"
11 #include "itemserializer_p.h"
12 #include "itemserializerplugin.h"
13 
14 #include "akonadicore_debug.h"
15 
16 // Qt
17 #include <QByteArray>
18 #include <QHash>
19 #include <QMimeDatabase>
20 #include <QMimeType>
21 #include <QRegularExpression>
22 #include <QStack>
23 #include <QString>
24 #include <QStringList>
25 
26 // temporary
27 #include "pluginloader_p.h"
28 
29 #include <cassert>
30 #include <vector>
31 
32 static const char LEGACY_NAME[] = "legacy";
33 static const char DEFAULT_NAME[] = "default";
34 static const char _APPLICATION_OCTETSTREAM[] = "application/octet-stream";
35 
36 namespace Akonadi
37 {
38 Q_GLOBAL_STATIC(DefaultItemSerializerPlugin, s_defaultItemSerializerPlugin) // NOLINT(readability-redundant-member-init)
39 
40 class PluginEntry
41 {
42 public:
43  PluginEntry()
44  : mPlugin(nullptr)
45  {
46  }
47 
48  explicit PluginEntry(const QString &identifier, QObject *plugin = nullptr)
49  : mIdentifier(identifier)
50  , mPlugin(plugin)
51  {
52  qCDebug(AKONADICORE_LOG) << " PLUGIN : identifier" << identifier;
53  }
54 
55  QObject *plugin() const
56  {
57  if (mPlugin) {
58  return mPlugin;
59  }
60 
61  QObject *object = PluginLoader::self()->createForName(mIdentifier);
62  if (!object) {
63  qCWarning(AKONADICORE_LOG) << "ItemSerializerPluginLoader: "
64  << "plugin" << mIdentifier << "is not valid!";
65 
66  // we try to use the default in that case
67  mPlugin = s_defaultItemSerializerPlugin;
68  }
69 
70  mPlugin = object;
71  if (!qobject_cast<ItemSerializerPlugin *>(mPlugin)) {
72  qCWarning(AKONADICORE_LOG) << "ItemSerializerPluginLoader: "
73  << "plugin" << mIdentifier << "doesn't provide interface ItemSerializerPlugin!";
74 
75  // we try to use the default in that case
76  mPlugin = s_defaultItemSerializerPlugin;
77  }
78 
79  Q_ASSERT(mPlugin);
80 
81  return mPlugin;
82  }
83 
84  const char *pluginClassName() const
85  {
86  return plugin()->metaObject()->className();
87  }
88 
89  QString identifier() const
90  {
91  return mIdentifier;
92  }
93 
94  bool operator<(const PluginEntry &other) const
95  {
96  return mIdentifier < other.mIdentifier;
97  }
98 
99  bool operator<(const QString &identifier) const
100  {
101  return mIdentifier < identifier;
102  }
103 
104 private:
105  QString mIdentifier;
106  mutable QObject *mPlugin;
107 };
108 
109 class MimeTypeEntry
110 {
111 public:
112  explicit MimeTypeEntry(const QString &mimeType)
113  : m_mimeType(mimeType)
114  {
115  }
116 
117  QString type() const
118  {
119  return m_mimeType;
120  }
121 
122  void add(const QByteArray &class_, const PluginEntry &entry)
123  {
124  m_pluginsByMetaTypeId.clear(); // iterators will be invalidated by next line
125  m_plugins.insert(class_, entry);
126  }
127 
128  const PluginEntry *plugin(const QByteArray &class_) const
129  {
130  const QHash<QByteArray, PluginEntry>::const_iterator it = m_plugins.find(class_);
131  return it == m_plugins.end() ? nullptr : it.operator->();
132  }
133 
134  const PluginEntry *defaultPlugin() const
135  {
136  // 1. If there's an explicit default plugin, use that one:
137  if (const PluginEntry *pe = plugin(DEFAULT_NAME)) {
138  return pe;
139  }
140 
141  // 2. Otherwise, look through the already instantiated plugins,
142  // and return one of them (preferably not the legacy one):
143  bool sawZero = false;
144  for (QMap<int, QHash<QByteArray, PluginEntry>::const_iterator>::const_iterator it = m_pluginsByMetaTypeId.constBegin(),
145  end = m_pluginsByMetaTypeId.constEnd();
146  it != end;
147  ++it) {
148  if (it.key() == 0) {
149  sawZero = true;
150  } else if (*it != m_plugins.end()) {
151  return it->operator->();
152  }
153  }
154 
155  // 3. Otherwise, look through the whole list (again, preferably not the legacy one):
156  for (QHash<QByteArray, PluginEntry>::const_iterator it = m_plugins.constBegin(), end = m_plugins.constEnd(); it != end; ++it) {
157  if (it.key() == LEGACY_NAME) {
158  sawZero = true;
159  } else {
160  return it.operator->();
161  }
162  }
163 
164  // 4. take the legacy one:
165  if (sawZero) {
166  return plugin(0);
167  }
168  return nullptr;
169  }
170 
171  const PluginEntry *plugin(int metaTypeId) const
172  {
173  const QMap<int, QHash<QByteArray, PluginEntry>::const_iterator> &c_pluginsByMetaTypeId = m_pluginsByMetaTypeId;
174  QMap<int, QHash<QByteArray, PluginEntry>::const_iterator>::const_iterator it = c_pluginsByMetaTypeId.find(metaTypeId);
175  if (it == c_pluginsByMetaTypeId.end()) {
177  m_pluginsByMetaTypeId.insert(metaTypeId, m_plugins.find(metaTypeId ? QMetaType::typeName(metaTypeId) : LEGACY_NAME)));
178  }
179  return *it == m_plugins.end() ? nullptr : it->operator->();
180  }
181 
182  const PluginEntry *plugin(const QVector<int> &metaTypeIds, int &chosen) const
183  {
184  bool sawZero = false;
185  for (QVector<int>::const_iterator it = metaTypeIds.begin(), end = metaTypeIds.end(); it != end; ++it) {
186  if (*it == 0) {
187  sawZero = true; // skip the legacy type and see if we can find something else first
188  } else if (const PluginEntry *const entry = plugin(*it)) {
189  chosen = *it;
190  return entry;
191  }
192  }
193  if (sawZero) {
194  chosen = 0;
195  return plugin(0);
196  }
197  return nullptr;
198  }
199 
200 private:
201  QString m_mimeType;
202  QHash<QByteArray /* class */, PluginEntry> m_plugins;
203  mutable QMap<int, QHash<QByteArray, PluginEntry>::const_iterator> m_pluginsByMetaTypeId;
204 };
205 
206 class PluginRegistry
207 {
208 public:
209  PluginRegistry()
210  : mDefaultPlugin(PluginEntry(QStringLiteral("application/[email protected]"), s_defaultItemSerializerPlugin))
211  , mOverridePlugin(nullptr)
212  {
213  const PluginLoader *pl = PluginLoader::self();
214  if (!pl) {
215  qCWarning(AKONADICORE_LOG) << "Cannot instantiate plugin loader!";
216  return;
217  }
218  const QStringList names = pl->names();
219  qCDebug(AKONADICORE_LOG) << "ItemSerializerPluginLoader: "
220  << "found" << names.size() << "plugins.";
222  QRegularExpression rx(QRegularExpression::anchoredPattern(QStringLiteral("(.+)@(.+)")));
223  QMimeDatabase mimeDb;
224  for (const QString &name : names) {
225  QRegularExpressionMatch match = rx.match(name);
226  if (match.hasMatch()) {
227  const QMimeType mime = mimeDb.mimeTypeForName(match.captured(1));
228  if (mime.isValid()) {
229  const QString mimeType = mime.name();
230  const QByteArray classType = match.captured(2).toLatin1();
231  QMap<QString, MimeTypeEntry>::iterator it = map.find(mimeType);
232  if (it == map.end()) {
233  it = map.insert(mimeType, MimeTypeEntry(mimeType));
234  }
235  it->add(classType, PluginEntry(name));
236  }
237  } else {
238  qCDebug(AKONADICORE_LOG) << "ItemSerializerPluginLoader: "
239  << "name" << name << "doesn't look like [email protected]";
240  }
241  }
242  const QString APPLICATION_OCTETSTREAM = QLatin1String(_APPLICATION_OCTETSTREAM);
243  QMap<QString, MimeTypeEntry>::iterator it = map.find(APPLICATION_OCTETSTREAM);
244  if (it == map.end()) {
245  it = map.insert(APPLICATION_OCTETSTREAM, MimeTypeEntry(APPLICATION_OCTETSTREAM));
246  }
247  it->add("QByteArray", mDefaultPlugin);
248  it->add(LEGACY_NAME, mDefaultPlugin);
249  const int size = map.size();
250  allMimeTypes.reserve(size);
251  std::copy(map.begin(), map.end(), std::back_inserter(allMimeTypes));
252  }
253 
254  QObject *findBestMatch(const QString &type, const QVector<int> &metaTypeId, TypePluginLoader::Options opt)
255  {
256  if (QObject *const plugin = findBestMatch(type, metaTypeId)) {
257  {
258  if ((opt & TypePluginLoader::NoDefault) && plugin == mDefaultPlugin.plugin()) {
259  return nullptr;
260  }
261  return plugin;
262  }
263  }
264  return nullptr;
265  }
266 
267  QObject *findBestMatch(const QString &type, const QVector<int> &metaTypeIds)
268  {
269  if (mOverridePlugin) {
270  return mOverridePlugin;
271  }
272  if (QObject *const plugin = cacheLookup(type, metaTypeIds)) {
273  // plugin cached, so let's take that one
274  return plugin;
275  }
276  int chosen = -1;
277  QObject *const plugin = findBestMatchImpl(type, metaTypeIds, chosen);
278  if (metaTypeIds.empty()) {
279  if (plugin) {
280  cachedDefaultPlugins[type] = plugin;
281  }
282  }
283  if (chosen >= 0) {
284  cachedPlugins[type][chosen] = plugin;
285  }
286  return plugin;
287  }
288 
289  void overrideDefaultPlugin(QObject *p)
290  {
291  mOverridePlugin = p;
292  }
293 
294 private:
295  // Returns plugin matches for a mimetype in best->worst order, in terms of mimetype specificity
296  void findSuitablePlugins(QMimeType mimeType, QSet<QMimeType> &checkedMimeTypes, QVector<int> &matchingIndexes, const QMimeDatabase &mimeDb) const
297  {
298  // Avoid adding duplicates to our matchingIndexes
299  if (checkedMimeTypes.contains(mimeType)) {
300  return;
301  }
302 
303  checkedMimeTypes.insert(mimeType);
304 
305  // Check each of the mimetypes we have plugins for to find a match
306  for (int i = 0, end = allMimeTypes.size(); i < end; ++i) {
307  const QMimeType pluginMimeType = mimeDb.mimeTypeForName(allMimeTypes[i].type()); // Convert from Akonadi::MimeTypeEntry
308  if (!pluginMimeType.isValid() || pluginMimeType != mimeType) {
309  continue;
310  }
311 
312  matchingIndexes.append(i); // We found a match! This mimetype is supported by one of our plugins
313  }
314 
315  auto parentTypes = mimeType.parentMimeTypes();
316 
317  // Recursively move up the mimetype tree (checking less specific mimetypes)
318  for (const auto &parent : parentTypes) {
319  QMimeType parentType = mimeDb.mimeTypeForName(parent);
320  findSuitablePlugins(parentType, checkedMimeTypes, matchingIndexes, mimeDb);
321  }
322  };
323 
324  QObject *findBestMatchImpl(const QString &type, const QVector<int> &metaTypeIds, int &chosen) const
325  {
326  const QMimeDatabase mimeDb;
327  const QMimeType mimeType = mimeDb.mimeTypeForName(type);
328  if (!mimeType.isValid()) {
329  qCWarning(AKONADICORE_LOG) << "Invalid mimetype requested:" << type;
330  return mDefaultPlugin.plugin();
331  }
332 
333  QSet<QMimeType> checkedMimeTypes;
334  QVector<int> matchingIndexes;
335 
336  findSuitablePlugins(mimeType, checkedMimeTypes, matchingIndexes, mimeDb);
337 
338  // Ask each one in turn if it can handle any of the metaTypeIds:
339  // qCDebug(AKONADICORE_LOG) << "Looking for " << format( type, metaTypeIds );
340  for (QVector<int>::const_iterator it = matchingIndexes.constBegin(), end = matchingIndexes.constEnd(); it != end; ++it) {
341  // qCDebug(AKONADICORE_LOG) << " Considering serializer plugin for type" << allMimeTypes[matchingIndexes[*it]].type()
342  // // << "as the closest match";
343  const MimeTypeEntry &mt = allMimeTypes[*it];
344  if (metaTypeIds.empty()) {
345  if (const PluginEntry *const entry = mt.defaultPlugin()) {
346  // qCDebug(AKONADICORE_LOG) << " -> got " << entry->pluginClassName() << " and am happy with it.";
347  // FIXME ? in qt5 we show "application/octet-stream" first so if will use default plugin. Exclude it until we look at all mimetype and use
348  // default at the end if necessary
349  if (allMimeTypes[*it].type() != QLatin1String("application/octet-stream")) {
350  return entry->plugin();
351  }
352  } else {
353  // qCDebug(AKONADICORE_LOG) << " -> no default plugin for this mime type, trying next";
354  }
355  } else if (const PluginEntry *const entry = mt.plugin(metaTypeIds, chosen)) {
356  // qCDebug(AKONADICORE_LOG) << " -> got " << entry->pluginClassName() << " and am happy with it.";
357  return entry->plugin();
358  } else {
359  // qCDebug(AKONADICORE_LOG) << " -> can't handle any of the types, trying next";
360  }
361  }
362 
363  // qCDebug(AKONADICORE_LOG) << " No further candidates, using default plugin";
364  // no luck? Use the default plugin
365  return mDefaultPlugin.plugin();
366  }
367 
368  std::vector<MimeTypeEntry> allMimeTypes; // All the mimetypes that we have plugins for
370  QHash<QString, QObject *> cachedDefaultPlugins;
371 
372  // ### cache NULLs, too
373  QObject *cacheLookup(const QString &mimeType, const QVector<int> &metaTypeIds) const
374  {
375  if (metaTypeIds.empty()) {
376  const QHash<QString, QObject *>::const_iterator hit = cachedDefaultPlugins.find(mimeType);
377  if (hit != cachedDefaultPlugins.end()) {
378  return *hit;
379  }
380  }
381 
382  const QHash<QString, QMap<int, QObject *>>::const_iterator hit = cachedPlugins.find(mimeType);
383  if (hit == cachedPlugins.end()) {
384  return nullptr;
385  }
386  bool sawZero = false;
387  for (QVector<int>::const_iterator it = metaTypeIds.begin(), end = metaTypeIds.end(); it != end; ++it) {
388  if (*it == 0) {
389  sawZero = true; // skip the legacy type and see if we can find something else first
390  } else if (QObject *const o = hit->value(*it)) {
391  return o;
392  }
393  }
394  if (sawZero) {
395  return hit->value(0);
396  }
397  return nullptr;
398  }
399 
400 private:
401  PluginEntry mDefaultPlugin;
402  QObject *mOverridePlugin;
403 };
404 
405 Q_GLOBAL_STATIC(PluginRegistry, s_pluginRegistry) // NOLINT(readability-redundant-member-init)
406 
407 QObject *TypePluginLoader::objectForMimeTypeAndClass(const QString &mimetype, const QVector<int> &metaTypeIds, Options opt)
408 {
409  return s_pluginRegistry->findBestMatch(mimetype, metaTypeIds, opt);
410 }
411 
412 QObject *TypePluginLoader::defaultObjectForMimeType(const QString &mimetype)
413 {
414  return objectForMimeTypeAndClass(mimetype, QVector<int>());
415 }
416 
417 ItemSerializerPlugin *TypePluginLoader::pluginForMimeTypeAndClass(const QString &mimetype, const QVector<int> &metaTypeIds, Options opt)
418 {
419  return qobject_cast<ItemSerializerPlugin *>(objectForMimeTypeAndClass(mimetype, metaTypeIds, opt));
420 }
421 
422 ItemSerializerPlugin *TypePluginLoader::defaultPluginForMimeType(const QString &mimetype)
423 {
424  ItemSerializerPlugin *plugin = qobject_cast<ItemSerializerPlugin *>(defaultObjectForMimeType(mimetype));
425  Q_ASSERT(plugin);
426  return plugin;
427 }
428 
429 void TypePluginLoader::overridePluginLookup(QObject *p)
430 {
431  s_pluginRegistry->overrideDefaultPlugin(p);
432 }
433 
434 } // namespace Akonadi
QString anchoredPattern(const QString &expression)
const T value(const Key &key) const const
QVector::iterator begin()
void append(const T &value)
QHash::iterator find(const Key &key)
QVector::const_iterator constEnd() const const
KCALUTILS_EXPORT QString mimeType()
Q_GLOBAL_STATIC(Internal::StaticControl, s_instance) class ControlPrivate
Definition: control.cpp:28
int size() const const
QMap::iterator end()
QMap::iterator find(const Key &key)
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
QHash::const_iterator constBegin() const const
const char * typeName(int typeId)
const Key key(const T &value) const const
KGuiItem add()
KIOCORE_EXPORT void cacheLookup(const QHostInfo &info)
bool isValid() const const
bool contains(const T &value) const const
QVector::iterator end()
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString name(StandardShortcut id)
QSet::iterator insert(const T &value)
QVector::const_iterator constBegin() const const
QFuture< void > map(Sequence &sequence, MapFunctor function)
const QList< QKeySequence > & end()
bool empty() const const
QHash::iterator end()
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Jul 2 2022 06:41:49 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.