1 | /* |
2 | Copyright (c) 2007 Till Adam <adam@kde.org> |
3 | Copyright (c) 2007 Volker Krause <vkrause@kde.org> |
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 | // KDE core |
28 | #include <kdebug.h> |
29 | #include <kmimetype.h> |
30 | #include <kglobal.h> |
31 | |
32 | // Qt |
33 | #include <QtCore/QHash> |
34 | #include <QtCore/QString> |
35 | #include <QtCore/QByteArray> |
36 | #include <QtCore/QStringList> |
37 | |
38 | #include <boost/graph/adjacency_list.hpp> |
39 | #include <boost/graph/topological_sort.hpp> |
40 | |
41 | // temporary |
42 | #include "pluginloader_p.h" |
43 | |
44 | #include <vector> |
45 | #include <cassert> |
46 | |
47 | static const char LEGACY_NAME[] = "legacy" ; |
48 | static const char DEFAULT_NAME[] = "default" ; |
49 | static const char _APPLICATION_OCTETSTREAM[] = "application/octet-stream" ; |
50 | |
51 | namespace Akonadi { |
52 | |
53 | K_GLOBAL_STATIC(DefaultItemSerializerPlugin, s_defaultItemSerializerPlugin) |
54 | |
55 | class PluginEntry |
56 | { |
57 | public: |
58 | PluginEntry() |
59 | : mPlugin(0) |
60 | { |
61 | } |
62 | |
63 | explicit PluginEntry(const QString &identifier, QObject *plugin = 0) |
64 | : mIdentifier(identifier) |
65 | , mPlugin(plugin) |
66 | { |
67 | } |
68 | |
69 | QObject *plugin() const |
70 | { |
71 | if (mPlugin) { |
72 | return mPlugin; |
73 | } |
74 | |
75 | QObject *object = PluginLoader::self()->createForName(mIdentifier); |
76 | if (!object) { |
77 | kWarning() << "ItemSerializerPluginLoader: " |
78 | << "plugin" << mIdentifier << "is not valid!" << endl; |
79 | |
80 | // we try to use the default in that case |
81 | mPlugin = s_defaultItemSerializerPlugin; |
82 | } |
83 | |
84 | mPlugin = object; |
85 | if (!qobject_cast<ItemSerializerPlugin *>(mPlugin)) { |
86 | kWarning() << "ItemSerializerPluginLoader: " |
87 | << "plugin" << mIdentifier << "doesn't provide interface ItemSerializerPlugin!" << endl; |
88 | |
89 | // we try to use the default in that case |
90 | mPlugin = s_defaultItemSerializerPlugin; |
91 | } |
92 | |
93 | Q_ASSERT(mPlugin); |
94 | |
95 | return mPlugin; |
96 | } |
97 | |
98 | const char *pluginClassName() const |
99 | { |
100 | return plugin()->metaObject()->className(); |
101 | } |
102 | |
103 | QString identifier() const |
104 | { |
105 | return mIdentifier; |
106 | } |
107 | |
108 | bool operator<(const PluginEntry &other) const |
109 | { |
110 | return mIdentifier < other.mIdentifier; |
111 | } |
112 | |
113 | bool operator<(const QString &identifier) const |
114 | { |
115 | return mIdentifier < identifier; |
116 | } |
117 | |
118 | private: |
119 | QString mIdentifier; |
120 | mutable QObject *mPlugin; |
121 | }; |
122 | |
123 | static bool operator<(const QString &identifier, const PluginEntry &entry) |
124 | { |
125 | return identifier < entry.identifier(); |
126 | } |
127 | |
128 | class MimeTypeEntry |
129 | { |
130 | public: |
131 | explicit MimeTypeEntry(const QString &mimeType) |
132 | : m_mimeType(mimeType) |
133 | , m_plugins() |
134 | , m_pluginsByMetaTypeId() |
135 | { |
136 | } |
137 | |
138 | QString type() const |
139 | { |
140 | return m_mimeType; |
141 | } |
142 | |
143 | void add(const QByteArray &class_, const PluginEntry &entry) |
144 | { |
145 | m_pluginsByMetaTypeId.clear(); // iterators will be invalidated by next line |
146 | m_plugins.insert(class_, entry); |
147 | } |
148 | |
149 | const PluginEntry *plugin(const QByteArray &class_) const |
150 | { |
151 | const QHash<QByteArray, PluginEntry>::const_iterator it = m_plugins.find(class_); |
152 | return it == m_plugins.end() ? 0 : it.operator->(); |
153 | } |
154 | |
155 | const PluginEntry *defaultPlugin() const |
156 | { |
157 | // 1. If there's an explicit default plugin, use that one: |
158 | if (const PluginEntry *pe = plugin(DEFAULT_NAME)) { |
159 | return pe; |
160 | } |
161 | |
162 | // 2. Otherwise, look through the already instantiated plugins, |
163 | // and return one of them (preferably not the legacy one): |
164 | bool sawZero = false; |
165 | for (QMap<int, QHash<QByteArray, PluginEntry>::const_iterator>::const_iterator it = m_pluginsByMetaTypeId.constBegin(), end = m_pluginsByMetaTypeId.constEnd(); it != end; ++it) { |
166 | if (it.key() == 0) { |
167 | sawZero = true; |
168 | } else if (*it != m_plugins.end()) { |
169 | return it->operator->(); |
170 | } |
171 | } |
172 | |
173 | // 3. Otherwise, look through the whole list (again, preferably not the legacy one): |
174 | for (QHash<QByteArray, PluginEntry>::const_iterator it = m_plugins.constBegin(), end = m_plugins.constEnd(); it != end; ++it) { |
175 | if (it.key() == LEGACY_NAME) { |
176 | sawZero = true; |
177 | } else { |
178 | return it.operator->(); |
179 | } |
180 | } |
181 | |
182 | // 4. take the legacy one: |
183 | if (sawZero) { |
184 | return plugin(0); |
185 | } |
186 | return 0; |
187 | } |
188 | |
189 | const PluginEntry *plugin(int metaTypeId) const |
190 | { |
191 | const QMap<int, QHash<QByteArray, PluginEntry>::const_iterator> &c_pluginsByMetaTypeId = m_pluginsByMetaTypeId; |
192 | QMap<int, QHash<QByteArray, PluginEntry>::const_iterator>::const_iterator it = c_pluginsByMetaTypeId.find(metaTypeId); |
193 | if (it == c_pluginsByMetaTypeId.end()) { |
194 | it = QMap<int, QHash<QByteArray, PluginEntry>::const_iterator>::const_iterator(m_pluginsByMetaTypeId.insert(metaTypeId, m_plugins.find(metaTypeId ? QMetaType::typeName(metaTypeId) : LEGACY_NAME))); |
195 | } |
196 | return *it == m_plugins.end() ? 0 : it->operator->(); |
197 | } |
198 | |
199 | const PluginEntry *plugin(const QVector<int> &metaTypeIds, int &chosen) const |
200 | { |
201 | bool sawZero = false; |
202 | for (QVector<int>::const_iterator it = metaTypeIds.begin(), end = metaTypeIds.end(); it != end; ++it) { |
203 | if (*it == 0) { |
204 | sawZero = true; // skip the legacy type and see if we can find something else first |
205 | } else if (const PluginEntry *const entry = plugin(*it)) { |
206 | chosen = *it; |
207 | return entry; |
208 | } |
209 | } |
210 | if (sawZero) { |
211 | chosen = 0; |
212 | return plugin(0); |
213 | } |
214 | return 0; |
215 | } |
216 | |
217 | private: |
218 | QString m_mimeType; |
219 | QHash<QByteArray/* class */, PluginEntry> m_plugins; |
220 | mutable QMap<int, QHash<QByteArray, PluginEntry>::const_iterator> m_pluginsByMetaTypeId; |
221 | }; |
222 | |
223 | static bool operator<(const MimeTypeEntry &lhs, const MimeTypeEntry &rhs) |
224 | { |
225 | return lhs.type() < rhs.type(); |
226 | } |
227 | |
228 | static bool operator<(const MimeTypeEntry &lhs, const QString &rhs) |
229 | { |
230 | return lhs.type() < rhs; |
231 | } |
232 | |
233 | static bool operator<(const QString &lhs, const MimeTypeEntry &rhs) |
234 | { |
235 | return lhs < rhs.type(); |
236 | } |
237 | |
238 | static QString format(const QString &mimeType, const QVector<int> &metaTypeIds) { |
239 | if (metaTypeIds.empty()) { |
240 | return QLatin1String("default for " ) + mimeType; |
241 | } |
242 | QStringList classTypes; |
243 | Q_FOREACH (int metaTypeId, metaTypeIds) { |
244 | classTypes.push_back(QString::fromLatin1(metaTypeId ? QMetaType::typeName(metaTypeId) : LEGACY_NAME)); |
245 | } |
246 | return mimeType + QLatin1String("@{" ) + classTypes.join(QLatin1String("," )) + QLatin1Char('}'); |
247 | } |
248 | |
249 | class PluginRegistry |
250 | { |
251 | public: |
252 | PluginRegistry() |
253 | : mDefaultPlugin(PluginEntry(QLatin1String("application/octet-stream@QByteArray" ), s_defaultItemSerializerPlugin)) |
254 | , mOverridePlugin(0) |
255 | { |
256 | const PluginLoader *pl = PluginLoader::self(); |
257 | if (!pl) { |
258 | kWarning() << "Cannot instantiate plugin loader!" << endl; |
259 | return; |
260 | } |
261 | const QStringList names = pl->names(); |
262 | kDebug() << "ItemSerializerPluginLoader: " |
263 | << "found" << names.size() << "plugins." << endl; |
264 | QMap<QString, MimeTypeEntry> map; |
265 | QRegExp rx(QLatin1String("(.+)@(.+)" )); |
266 | Q_FOREACH (const QString &name, names) { |
267 | if (rx.exactMatch(name)) { |
268 | KMimeType::Ptr mime = KMimeType::mimeType(rx.cap(1), KMimeType::ResolveAliases); |
269 | if (mime) { |
270 | const QString mimeType = mime->name(); |
271 | const QByteArray classType = rx.cap(2).toLatin1(); |
272 | QMap<QString, MimeTypeEntry>::iterator it = map.find(mimeType); |
273 | if (it == map.end()) { |
274 | it = map.insert(mimeType, MimeTypeEntry(mimeType)); |
275 | } |
276 | it->add(classType, PluginEntry(name)); |
277 | } |
278 | } else { |
279 | kDebug() << "ItemSerializerPluginLoader: " |
280 | << "name" << name << "doesn't look like mimetype@classtype" << endl; |
281 | } |
282 | } |
283 | const QString APPLICATION_OCTETSTREAM = QLatin1String(_APPLICATION_OCTETSTREAM); |
284 | QMap<QString, MimeTypeEntry>::iterator it = map.find(APPLICATION_OCTETSTREAM); |
285 | if (it == map.end()) { |
286 | it = map.insert(APPLICATION_OCTETSTREAM, MimeTypeEntry(APPLICATION_OCTETSTREAM)); |
287 | } |
288 | it->add("QByteArray" , mDefaultPlugin); |
289 | it->add(LEGACY_NAME, mDefaultPlugin); |
290 | const int size = map.size(); |
291 | allMimeTypes.reserve(size); |
292 | std::copy(map.begin(), map.end(), std::back_inserter(allMimeTypes)); |
293 | } |
294 | |
295 | QObject *findBestMatch(const QString &type, const QVector<int> &metaTypeId, TypePluginLoader::Options opt) |
296 | { |
297 | if (QObject *const plugin = findBestMatch(type, metaTypeId)) { { |
298 | if ((opt &TypePluginLoader::NoDefault) && plugin == mDefaultPlugin.plugin()) { |
299 | return 0; |
300 | } |
301 | return plugin; |
302 | } |
303 | } |
304 | return 0; |
305 | } |
306 | |
307 | QObject *findBestMatch(const QString &type, const QVector<int> &metaTypeIds) |
308 | { |
309 | if (mOverridePlugin) { |
310 | return mOverridePlugin; |
311 | } |
312 | if (QObject *const plugin = cacheLookup(type, metaTypeIds)) { |
313 | // plugin cached, so let's take that one |
314 | return plugin; |
315 | } |
316 | int chosen = -1; |
317 | QObject *const plugin = findBestMatchImpl(type, metaTypeIds, chosen); |
318 | if (metaTypeIds.empty()) { |
319 | if (plugin) { |
320 | cachedDefaultPlugins[type] = plugin; |
321 | } |
322 | } |
323 | if (chosen >= 0) { |
324 | cachedPlugins[type][chosen] = plugin; |
325 | } |
326 | return plugin; |
327 | } |
328 | |
329 | void overrideDefaultPlugin(QObject *p) |
330 | { |
331 | mOverridePlugin = p; |
332 | } |
333 | |
334 | private: |
335 | QObject *findBestMatchImpl(const QString &type, const QVector<int> &metaTypeIds, int &chosen) const |
336 | { |
337 | KMimeType::Ptr mimeType = KMimeType::mimeType(type, KMimeType::ResolveAliases); |
338 | if (mimeType.isNull()) { |
339 | return mDefaultPlugin.plugin(); |
340 | } |
341 | |
342 | // step 1: find all plugins that match at all |
343 | QVector<int> matchingIndexes; |
344 | for (int i = 0, end = allMimeTypes.size(); i < end; ++i) { |
345 | if (mimeType->is(allMimeTypes[i].type())) { |
346 | matchingIndexes.append(i); |
347 | } |
348 | } |
349 | |
350 | // step 2: if we have more than one match, find the most specific one using topological sort |
351 | QVector<int> order; |
352 | if (matchingIndexes.size() <= 1) { |
353 | order.push_back(0); |
354 | } else { |
355 | boost::adjacency_list<> graph(matchingIndexes.size()); |
356 | for (int i = 0, end = matchingIndexes.size(); i != end; ++i) { |
357 | KMimeType::Ptr mimeType = KMimeType::mimeType(allMimeTypes[matchingIndexes[i]].type(), KMimeType::ResolveAliases); |
358 | if (mimeType.isNull()) { |
359 | continue; |
360 | } |
361 | for (int j = 0; j != end; ++j) { |
362 | if (i != j && mimeType->is(allMimeTypes[matchingIndexes[j]].type())) { |
363 | boost::add_edge(j, i, graph); |
364 | } |
365 | } |
366 | } |
367 | |
368 | order.reserve(matchingIndexes.size()); |
369 | try { |
370 | boost::topological_sort(graph, std::back_inserter(order)); |
371 | } catch (boost::not_a_dag &e) { |
372 | kWarning() << "Mimetype tree is not a DAG!" ; |
373 | return mDefaultPlugin.plugin(); |
374 | } |
375 | } |
376 | |
377 | // step 3: ask each one in turn if it can handle any of the metaTypeIds: |
378 | // kDebug() << "Looking for " << format( type, metaTypeIds ); |
379 | for (QVector<int>::const_iterator it = order.constBegin(), end = order.constEnd(); it != end; ++it) { |
380 | // kDebug() << " Considering serializer plugin for type" << allMimeTypes[matchingIndexes[*it]].type() |
381 | // // << "as the closest match"; |
382 | const MimeTypeEntry &mt = allMimeTypes[matchingIndexes[*it]]; |
383 | if (metaTypeIds.empty()) { |
384 | if (const PluginEntry *const entry = mt.defaultPlugin()) { |
385 | // kDebug() << " -> got " << entry->pluginClassName() << " and am happy with it."; |
386 | return entry->plugin(); |
387 | } else { |
388 | // kDebug() << " -> no default plugin for this mime type, trying next"; |
389 | } |
390 | } else if (const PluginEntry *const entry = mt.plugin(metaTypeIds, chosen)) { |
391 | // kDebug() << " -> got " << entry->pluginClassName() << " and am happy with it."; |
392 | return entry->plugin(); |
393 | } else { |
394 | // kDebug() << " -> can't handle any of the types, trying next"; |
395 | } |
396 | } |
397 | |
398 | // kDebug() << " No further candidates, using default plugin"; |
399 | // no luck? Use the default plugin |
400 | return mDefaultPlugin.plugin(); |
401 | } |
402 | |
403 | std::vector<MimeTypeEntry> allMimeTypes; |
404 | QHash<QString, QMap<int, QObject *> > cachedPlugins; |
405 | QHash<QString, QObject *> cachedDefaultPlugins; |
406 | |
407 | // ### cache NULLs, too |
408 | QObject *cacheLookup(const QString &mimeType, const QVector<int> &metaTypeIds) const { |
409 | if (metaTypeIds.empty()) { |
410 | const QHash<QString, QObject *>::const_iterator hit = cachedDefaultPlugins.find(mimeType); |
411 | if (hit != cachedDefaultPlugins.end()) { |
412 | return *hit; |
413 | } |
414 | } |
415 | |
416 | const QHash<QString, QMap<int, QObject *> >::const_iterator hit = cachedPlugins.find(mimeType); |
417 | if (hit == cachedPlugins.end()) { |
418 | return 0; |
419 | } |
420 | bool sawZero = false; |
421 | for (QVector<int>::const_iterator it = metaTypeIds.begin(), end = metaTypeIds.end(); it != end; ++it) { |
422 | if (*it == 0) { |
423 | sawZero = true; // skip the legacy type and see if we can find something else first |
424 | } else if (QObject *const o = hit->value(*it)) { |
425 | return o; |
426 | } |
427 | } |
428 | if (sawZero) { |
429 | return hit->value(0); |
430 | } |
431 | return 0; |
432 | } |
433 | |
434 | private: |
435 | PluginEntry mDefaultPlugin; |
436 | QObject *mOverridePlugin; |
437 | }; |
438 | |
439 | K_GLOBAL_STATIC(PluginRegistry, s_pluginRegistry) |
440 | |
441 | QObject *TypePluginLoader::objectForMimeTypeAndClass(const QString &mimetype, const QVector<int> &metaTypeIds, Options opt) |
442 | { |
443 | return s_pluginRegistry->findBestMatch(mimetype, metaTypeIds, opt); |
444 | } |
445 | |
446 | #if 0 |
447 | QObject *TypePluginLoader::legacyObjectForMimeType(const QString &mimetype) { |
448 | // ### impl specifically - only works b/c vector isn't used in impl ### |
449 | return objectForMimeTypeAndClass(mimetype, QVector<int>(1, 0)); |
450 | } |
451 | #endif |
452 | |
453 | QObject *TypePluginLoader::defaultObjectForMimeType(const QString &mimetype) { |
454 | return objectForMimeTypeAndClass(mimetype, QVector<int>()); |
455 | } |
456 | |
457 | ItemSerializerPlugin *TypePluginLoader::pluginForMimeTypeAndClass(const QString &mimetype, const QVector<int> &metaTypeIds, Options opt) |
458 | { |
459 | return qobject_cast<ItemSerializerPlugin *>(objectForMimeTypeAndClass(mimetype, metaTypeIds, opt)); |
460 | } |
461 | |
462 | #if 0 |
463 | ItemSerializerPlugin *TypePluginLoader::legacyPluginForMimeType(const QString &mimetype) { |
464 | ItemSerializerPlugin *plugin = qobject_cast<ItemSerializerPlugin *>(legacyObjectForMimeType(mimetype)); |
465 | Q_ASSERT(plugin); |
466 | return plugin; |
467 | } |
468 | #endif |
469 | |
470 | ItemSerializerPlugin *TypePluginLoader::defaultPluginForMimeType(const QString &mimetype) { |
471 | ItemSerializerPlugin *plugin = qobject_cast<ItemSerializerPlugin *>(defaultObjectForMimeType(mimetype)); |
472 | Q_ASSERT(plugin); |
473 | return plugin; |
474 | } |
475 | |
476 | void TypePluginLoader::overridePluginLookup(QObject *p) { |
477 | s_pluginRegistry->overrideDefaultPlugin(p); |
478 | } |
479 | |
480 | } |
481 | |