Akonadi

typepluginloader.cpp
1/*
2 SPDX-FileCopyrightText: 2007 Till Adam <adam@kde.org>
3 SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org>
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
32static const char LEGACY_NAME[] = "legacy";
33static const char DEFAULT_NAME[] = "default";
34static const char _APPLICATION_OCTETSTREAM[] = "application/octet-stream";
35
36namespace Akonadi
37{
38Q_GLOBAL_STATIC(DefaultItemSerializerPlugin, s_defaultItemSerializerPlugin) // NOLINT(readability-redundant-member-init)
39
40class PluginEntry
41{
42public:
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
104private:
105 QString mIdentifier;
106 mutable QObject *mPlugin;
107};
108
109class MimeTypeEntry
110{
111public:
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(metaTypeId).name() : LEGACY_NAME)));
178 }
179 return *it == m_plugins.end() ? nullptr : it->operator->();
180 }
181
182 const PluginEntry *plugin(const QList<int> &metaTypeIds, int &chosen) const
183 {
184 bool sawZero = false;
185 for (QList<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
200private:
201 QString m_mimeType;
202 QHash<QByteArray /* class */, PluginEntry> m_plugins;
204};
205
206class PluginRegistry
207{
208public:
209 PluginRegistry()
210 : mDefaultPlugin(PluginEntry(QStringLiteral("application/octet-stream@QByteArray"), 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();
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 mimetype@classtype";
240 }
241 }
242 const QString APPLICATION_OCTETSTREAM = QLatin1StringView(_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 QList<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 QList<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
294private:
295 // Returns plugin matches for a mimetype in best->worst order, in terms of mimetype specificity
296 void findSuitablePlugins(QMimeType mimeType, QSet<QMimeType> &checkedMimeTypes, QList<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 QList<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 QList<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 (QList<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() != QLatin1StringView("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 QList<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 (QList<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
400private:
401 PluginEntry mDefaultPlugin;
402 QObject *mOverridePlugin;
403};
404
405Q_GLOBAL_STATIC(PluginRegistry, s_pluginRegistry) // NOLINT(readability-redundant-member-init)
406
407QObject *TypePluginLoader::objectForMimeTypeAndClass(const QString &mimetype, const QList<int> &metaTypeIds, Options opt)
408{
409 return s_pluginRegistry->findBestMatch(mimetype, metaTypeIds, opt);
410}
411
412QObject *TypePluginLoader::defaultObjectForMimeType(const QString &mimetype)
413{
414 return objectForMimeTypeAndClass(mimetype, QList<int>());
415}
416
417ItemSerializerPlugin *TypePluginLoader::pluginForMimeTypeAndClass(const QString &mimetype, const QList<int> &metaTypeIds, Options opt)
418{
419 return qobject_cast<ItemSerializerPlugin *>(objectForMimeTypeAndClass(mimetype, metaTypeIds, opt));
420}
421
422ItemSerializerPlugin *TypePluginLoader::defaultPluginForMimeType(const QString &mimetype)
423{
424 ItemSerializerPlugin *plugin = qobject_cast<ItemSerializerPlugin *>(defaultObjectForMimeType(mimetype));
425 Q_ASSERT(plugin);
426 return plugin;
427}
428
429void TypePluginLoader::overridePluginLookup(QObject *p)
430{
431 s_pluginRegistry->overrideDefaultPlugin(p);
432}
433
434} // namespace Akonadi
Helper integration between Akonadi and Qt.
KCALUTILS_EXPORT QString mimeType()
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString name(GameStandardAction id)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
const_iterator constBegin() const const
const_iterator constEnd() const const
iterator end()
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
T value(const Key &key) const const
void append(QList< T > &&value)
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
bool empty() const const
iterator end()
qsizetype size() const const
iterator end()
iterator find(const Key &key)
const char * className() const const
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
bool isValid() const const
virtual const QMetaObject * metaObject() const const
QString anchoredPattern(QStringView expression)
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:31:58 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.