Akonadi

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

KDE's Doxygen guidelines are available online.