1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtOrganizer module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL21$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** $QT_END_LICENSE$
31**
32****************************************************************************/
33
34#include "qorganizermanager_p.h"
35
36#include <QtCore/qcoreapplication.h>
37#if !defined(QT_NO_DEBUG)
38#include <QtCore/qdebug.h>
39#endif
40#include <QtCore/qpluginloader.h>
41#include <QtCore/private/qfactoryloader_p.h>
42
43#include "qorganizeritemobserver.h"
44#include "qorganizermanagerenginefactory.h"
45
46QT_BEGIN_NAMESPACE_ORGANIZER
47
48QHash<QString, QOrganizerManagerEngineFactory *> QOrganizerManagerData::m_engines;
49bool QOrganizerManagerData::m_discovered;
50bool QOrganizerManagerData::m_discoveredStatic;
51QStringList QOrganizerManagerData::m_pluginPaths;
52
53#ifndef QT_NO_LIBRARY
54Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, (QT_ORGANIZER_MANAGER_ENGINE_INTERFACE, QLatin1String("/organizer")))
55#endif
56
57static void qOrganizerItemsCleanEngines()
58{
59 QOrganizerManagerData::m_discovered = false;
60 QList<QOrganizerManagerEngineFactory *> factories = QOrganizerManagerData::m_engines.values();
61 for (int i=0; i < factories.count(); i++)
62 delete factories.at(i);
63 QOrganizerManagerData::m_engines.clear();
64}
65
66void QOrganizerManagerData::createEngine(const QString &managerName, const QMap<QString, QString> &parameters)
67{
68 m_engine = 0;
69
70 QString builtManagerName = managerName.isEmpty() ? QOrganizerManager::availableManagers().value(i: 0) : managerName;
71
72 bool found = false;
73 bool loadedDynamic = false;
74
75 /* First check static factories */
76 loadStaticFactories();
77
78 /* See if we got a fast hit */
79 QList<QOrganizerManagerEngineFactory *> factories = m_engines.values(akey: builtManagerName);
80 m_lastError = QOrganizerManager::NoError;
81
82 while (!found) {
83 foreach (QOrganizerManagerEngineFactory *f, factories) {
84 m_engine = f->engine(parameters, error: &m_lastError);
85 if (m_engine) {
86 found = true;
87 break;
88 }
89 }
90
91 // Break if found or if this is the second time through
92 if (loadedDynamic || found)
93 break;
94
95 // otherwise load dynamic factories and reloop
96 loadFactories();
97 factories = m_engines.values(akey: builtManagerName);
98 loadedDynamic = true;
99 }
100
101 if (!m_engine) {
102 if (m_lastError == QOrganizerManager::NoError)
103 m_lastError = QOrganizerManager::DoesNotExistError;
104 m_engine = new QOrganizerManagerEngine();
105 }
106}
107
108void QOrganizerManagerData::loadStaticFactories()
109{
110 if (!m_discoveredStatic) {
111#if !defined QT_NO_DEBUG
112 const bool showDebug = qgetenv(varName: "QT_DEBUG_PLUGINS").toInt() > 0;
113#endif
114
115 m_discoveredStatic = true;
116
117 /* Clean stuff up at the end */
118 qAddPostRoutine(qOrganizerItemsCleanEngines);
119
120 /* Loop over all the static plugins */
121 QObjectList staticPlugins = QPluginLoader::staticInstances();
122 for (int i = 0; i < staticPlugins.count(); i++ ){
123 QOrganizerManagerEngineFactory *f = qobject_cast<QOrganizerManagerEngineFactory *>(object: staticPlugins.at(i));
124 if (f) {
125 QString name = f->managerName();
126#if !defined QT_NO_DEBUG
127 if (showDebug)
128 qDebug() << "Static: found an engine plugin" << f << "with name" << name;
129#endif
130 if (name != QStringLiteral("invalid") && !name.isEmpty()) {
131 // we also need to ensure that we haven't already loaded this factory.
132 if (m_engines.keys().contains(t: name))
133 qWarning(msg: "Static organizeritems plugin %s has the same name as a currently loaded plugin; ignored", qPrintable(name));
134 else
135 m_engines.insertMulti(key: name, value: f);
136 } else {
137 qWarning(msg: "Static organizeritems plugin with reserved name %s ignored", qPrintable(name));
138 }
139 }
140 }
141 }
142}
143
144/* Plugin loader */
145void QOrganizerManagerData::loadFactories()
146{
147#if !defined QT_NO_DEBUG
148 const bool showDebug = qgetenv(varName: "QT_DEBUG_PLUGINS").toInt() > 0;
149#endif
150
151 // Always do this..
152 loadStaticFactories();
153
154 QFactoryLoader *l = loader();
155 const QStringList keys = l->keyMap().values();
156 if (!m_discovered || keys != m_pluginPaths) {
157 m_discovered = true;
158 m_pluginPaths = keys;
159
160 for (int i = 0; i < keys.size(); ++i) {
161 QOrganizerManagerEngineFactory *f = qobject_cast<QOrganizerManagerEngineFactory *>(object: l->instance(index: i));
162 if (f) {
163 const QString name = f->managerName();
164#if !defined QT_NO_DEBUG
165 if (showDebug)
166 qDebug() << "Dynamic: found a organizer engine plugin" << f << "with name" << name;
167#endif
168 if (name != QStringLiteral("invalid") && !name.isEmpty()) {
169 // we also need to ensure that we haven't already loaded this factory.
170 if (m_engines.keys().contains(t: name))
171 qWarning(msg: "Organizer plugin %s has the same name as currently loaded plugin %s; ignored", qPrintable(m_pluginPaths.at(i)), qPrintable(name));
172 else
173 m_engines.insertMulti(key: name, value: f);
174 } else {
175 qWarning(msg: "Organizer plugin %s with reserved name %s ignored", qPrintable(m_pluginPaths.at(i)), qPrintable(name));
176 }
177 }
178
179#if !defined QT_NO_DEBUG
180 if (showDebug && !f) {
181 qDebug() << "Unknown plugin!";
182 if (const QObject *instance = l->instance(index: i))
183 qDebug() << "[qobject:" << instance << "]";
184 }
185#endif
186 }
187 }
188}
189
190void QOrganizerManagerData::registerObserver(QOrganizerItemObserver *observer)
191{
192 m_observerForItem.insert(akey: observer->itemId(), avalue: observer);
193}
194
195void QOrganizerManagerData::unregisterObserver(QOrganizerItemObserver *observer)
196{
197 QOrganizerItemId key = m_observerForItem.key(avalue: observer);
198 if (!key.isNull())
199 m_observerForItem.remove(key, value: observer);
200}
201
202void QOrganizerManagerData::_q_itemsUpdated(const QList<QOrganizerItemId> &ids, const QList<QOrganizerItemDetail::DetailType> &typesChanged)
203{
204 foreach (QOrganizerItemId id, ids) {
205 QList<QOrganizerItemObserver *> observers = m_observerForItem.values(akey: id);
206 foreach (QOrganizerItemObserver *observer, observers)
207 QMetaObject::invokeMethod(obj: observer, member: "itemChanged", Q_ARG(QList<QOrganizerItemDetail::DetailType>, typesChanged));
208 }
209}
210
211void QOrganizerManagerData::_q_itemsDeleted(const QList<QOrganizerItemId> &ids)
212{
213 foreach (QOrganizerItemId id, ids) {
214 QList<QOrganizerItemObserver *> observers = m_observerForItem.values(akey: id);
215 foreach (QOrganizerItemObserver *observer, observers)
216 QMetaObject::invokeMethod(obj: observer, member: "itemRemoved");
217 }
218}
219
220QOrganizerManagerData *QOrganizerManagerData::get(const QOrganizerManager *manager)
221{
222 return manager->d;
223}
224
225QOrganizerManagerEngine *QOrganizerManagerData::engine(const QOrganizerManager *manager)
226{
227 if (manager)
228 return manager->d->m_engine;
229 return 0;
230}
231
232static inline QString escapeParam(const QString &param)
233{
234 QString ret;
235 const int len = param.length();
236 ret.reserve(asize: len + (len >> 3));
237 for (QString::const_iterator it = param.begin(), end = param.end(); it != end; ++it) {
238 switch (it->unicode()) {
239 case ':':
240 ret += QStringLiteral("&#58;");
241 break;
242 case '=':
243 ret += QStringLiteral("&equ;");
244 break;
245 case '&':
246 ret += QStringLiteral("&amp;");
247 break;
248 default:
249 ret += *it;
250 break;
251 }
252 }
253 return ret;
254}
255
256static inline QByteArray escapeColon(const QByteArray &param)
257{
258 QByteArray ret;
259 const int len = param.length();
260 ret.reserve(asize: len + (len >> 3));
261 for (QByteArray::const_iterator it = param.begin(), end = param.end(); it != end; ++it) {
262 switch (*it) {
263 case ':':
264 ret += "&#58;";
265 break;
266 default:
267 ret += *it;
268 break;
269 }
270 }
271 return ret;
272}
273
274static inline QString unescapeParam(const QString &param)
275{
276 QString ret(param);
277 int index = 0;
278 while ((index = ret.indexOf(c: QLatin1Char('&'), from: index)) != -1) {
279 const QString partial(ret.mid(position: index, n: 5));
280 if (partial == QStringLiteral("&#58;"))
281 ret.replace(i: index, len: 5, QStringLiteral(":"));
282 else if (partial == QStringLiteral("&equ;"))
283 ret.replace(i: index, len: 5, QStringLiteral("="));
284 else if (partial == QStringLiteral("&amp;"))
285 ret.replace(i: index, len: 5, QStringLiteral("&"));
286 ++index;
287 }
288 return ret;
289}
290
291static inline QByteArray unescapeColon(const QByteArray &param)
292{
293 QByteArray ret(param);
294 int index = 0;
295 while ((index = ret.indexOf(c: '&', from: index)) != -1) {
296 const QByteArray partial(ret.mid(index, len: 5));
297 if (partial == "&#58;")
298 ret.replace(index, len: 5, s: ":");
299 ++index;
300 }
301 return ret;
302}
303
304/*!
305 Parses the individual components of the given \a uriString and fills the
306 \a managerName, \a params and \a managerUri and \a localId.
307 Returns true if the parts could be parsed successfully, false otherwise.
308*/
309bool QOrganizerManagerData::parseUri(const QString &uriString, QString *managerName, QMap<QString, QString> *params, bool strict)
310{
311 // Format: qtorganizer:<managerid>:<key>=<value>&<key>=<value>
312 // we assume that the prefix, managerid, and params cannot contain `:', `=', or `&'
313 // similarly, that neither param keys nor param values can contain these characters.
314
315 const QStringList colonSplit = uriString.split(sep: QLatin1Char(':'), behavior: QString::KeepEmptyParts);
316 if ((colonSplit.size() != 3) && (strict || colonSplit.size() != 2))
317 return false;
318
319 const QString prefix = colonSplit.at(i: 0);
320 const QString mgrName = colonSplit.at(i: 1);
321 const QString paramString = colonSplit.value(i: 2);
322
323 if (prefix != QStringLiteral("qtorganizer") || mgrName.isEmpty())
324 return false;
325
326 if (!paramString.isEmpty()) {
327 // Now we have to decode each parameter
328 QMap<QString, QString> outParams;
329 const QStringList pairs = paramString.split(sep: QRegExp(QStringLiteral("&(?!(amp;|equ;|#))")), behavior: QString::KeepEmptyParts);
330 for (int i = 0; i < pairs.size(); ++i) {
331 // This should be something like "foo&amp;bar&equ;=grob&amp;"
332 const QStringList pair = pairs.at(i).split(sep: QLatin1Char('='), behavior: QString::KeepEmptyParts);
333 if (pair.size() != 2)
334 return false;
335
336 QString arg = pair.at(i: 0);
337 QString param = pair.at(i: 1);
338
339 if (arg.isEmpty())
340 return false;
341
342 arg = unescapeParam(param: arg);
343 param = unescapeParam(param);
344
345 outParams.insert(akey: arg, avalue: param);
346 }
347
348 if (params)
349 *params = outParams;
350 }
351
352 if (managerName)
353 *managerName = unescapeParam(param: mgrName);
354
355 return true;
356}
357
358/*!
359 Returns an ID string that describes a manager name and parameters with which to instantiate
360 a manager object, from the given \a managerName and \a params.
361 If \a localId is non-null, the generated ID string is suitable for
362 passing to QOrganizerCollectionId::fromString() or QOrganizerItemId::fromString().
363*/
364QString QOrganizerManagerData::buildUri(const QString &managerName, const QMap<QString, QString> &params)
365{
366 // Format: qtorganizer:<managerid>:<key>=<value>&<key>=<value>
367 // if the prefix, managerid, param keys, or param values contain `:', `=', or `&',
368 // we escape them to `&#58;', `&equ;', and `&amp;', respectively.
369
370 QString paramString;
371 QMap<QString, QString>::const_iterator it = params.constBegin();
372 for ( ; it != params.constEnd(); ++it) {
373 if (it.key().isEmpty())
374 continue;
375 if (!paramString.isEmpty())
376 paramString += QLatin1Char('&');
377 paramString += escapeParam(param: it.key()) + QLatin1Char('=') + escapeParam(param: it.value());
378 }
379
380 return QStringLiteral("qtorganizer:") + escapeParam(param: managerName) + QLatin1Char(':') + paramString;
381}
382
383/*!
384 Parses the individual components of the given \a idData and fills the
385 \a managerName, \a params, \a managerUri and \a localId.
386 Returns true if the parts could be parsed successfully, false otherwise.
387*/
388bool QOrganizerManagerData::parseIdData(const QByteArray &idData, QString *managerName, QMap<QString, QString> *params, QString *managerUri, QByteArray *localId)
389{
390 // Format: <managerUri>:<localId>
391 int splitIndex = idData.lastIndexOf(c: ':');
392 if (splitIndex == -1)
393 return false;
394
395 const QString uriString(QString::fromUtf8(str: idData.mid(index: 0, len: splitIndex)));
396 if (!parseUri(uriString, managerName, params))
397 return false;
398
399 if (managerUri)
400 *managerUri = uriString;
401 if (localId)
402 *localId = unescapeColon(param: idData.mid(index: splitIndex + 1));
403
404 return true;
405}
406
407/*!
408 Returns an ID string that describes a manager name and parameters with which to instantiate
409 a manager object, from the given \a managerUri.
410 If \a localId is non-null, the generated ID string is suitable for
411 passing to QOrganizerCollectionId::fromString() or QOrganizerItemId::fromString().
412*/
413QByteArray QOrganizerManagerData::buildIdData(const QString &managerUri, const QByteArray &localId)
414{
415 // Format: <managerUri>:<localId>
416 // localId cannot contain ':' so it must be escaped
417 QByteArray rv = managerUri.toUtf8();
418 if (!localId.isEmpty())
419 rv.append(c: ':').append(a: escapeColon(param: localId));
420 return rv;
421}
422
423/*!
424 Returns an ID string that describes a manager name and parameters with which to instantiate
425 a manager object, from the given \a managerName and \a params.
426 If \a localId is non-null, the generated ID string is suitable for
427 passing to QOrganizerCollectionId::fromString() or QOrganizerItemId::fromString().
428*/
429QByteArray QOrganizerManagerData::buildIdData(const QString &managerName, const QMap<QString, QString> &params, const QByteArray &localId)
430{
431 return buildIdData(managerUri: buildUri(managerName, params), localId);
432}
433
434/*!
435 Returns a cached instance of the manager URI string that matches \a managerUri.
436 This instance should be preferred when constructing ID objects in order to promote
437 data sharing of the URI string.
438*/
439QString QOrganizerManagerData::cachedUri(const QString &managerUri)
440{
441 static QStringList managerUris;
442
443 int index = managerUris.indexOf(t: managerUri);
444 if (index != -1)
445 return managerUris.at(i: index);
446
447 managerUris.append(t: managerUri);
448 return managerUri;
449}
450
451QT_END_NAMESPACE_ORGANIZER
452

source code of qtpim/src/organizer/qorganizermanager_p.cpp