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 QtContacts 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 "qcontactmanager.h"
35#include "qcontactmanager_p.h"
36
37#if !defined(QT_NO_DEBUG)
38#include <QtCore/qdebug.h>
39#endif
40#include <QtCore/qdir.h>
41#include <QtCore/qfile.h>
42#include <QtCore/qjsonarray.h>
43#include <QtCore/qpluginloader.h>
44#include <QtCore/qpointer.h>
45#include <QtCore/private/qfactoryloader_p.h>
46
47#include "qcontact_p.h"
48#include "qcontactaction.h"
49#include "qcontactactiondescriptor.h"
50#include "qcontactmanagerenginefactory.h"
51
52#include "qcontactinvalidbackend_p.h"
53#include "qcontactspluginsearch_p.h"
54
55QT_BEGIN_NAMESPACE_CONTACTS
56
57/* Shared QContactManager stuff here, default engine stuff below */
58QHash<QString, QContactManagerEngineFactory*> QContactManagerData::m_engines;
59QSet<QContactManager*> QContactManagerData::m_aliveEngines;
60QList<QContactActionManagerPlugin*> QContactManagerData::m_actionManagers;
61
62bool QContactManagerData::m_discoveredStatic;
63QList<QJsonObject> QContactManagerData::m_pluginPaths;
64QList<QJsonObject> QContactManagerData::m_metaData;
65QStringList QContactManagerData::m_managerNames;
66
67
68#ifndef QT_NO_LIBRARY
69Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, (QT_CONTACT_MANAGER_ENGINE_INTERFACE, QLatin1String("/contacts")))
70#endif
71
72static void qContactsCleanEngines()
73{
74 // This is complicated by needing to remove any engines before we unload factories
75 // guard pointers as one engine could be parent of another manager and cause doubledelete
76 QList<QPointer<QContactManager> > aliveManagers;
77 foreach(QContactManager* manager, QContactManagerData::m_aliveEngines) {
78 aliveManagers << QPointer<QContactManager>(manager);
79 }
80
81 foreach(QPointer<QContactManager> manager, aliveManagers) {
82 if (!manager) {
83 // deleting engine of one manager, could cause deleting next manager in list (aggregation case)
84 continue;
85 }
86 // We don't delete the managers here, we just kill their engines
87 // and replace it with an invalid engine (for safety :/)
88 QContactManagerData* d = QContactManagerData::managerData(manager: manager.data());
89
90 delete d->m_engine;
91 d->m_engine = new QContactInvalidEngine();
92 }
93
94 QList<QContactManagerEngineFactory*> factories = QContactManagerData::m_engines.values();
95
96 for (int i=0; i < factories.count(); i++) {
97 delete factories.at(i);
98 }
99 QContactManagerData::m_engines.clear();
100 QContactManagerData::m_actionManagers.clear();
101 QContactManagerData::m_aliveEngines.clear();
102 QContactManagerData::m_pluginPaths.clear();
103 QContactManagerData::m_metaData.clear();
104 QContactManagerData::m_managerNames.clear();
105}
106
107static int parameterValue(const QMap<QString, QString> &parameters, const char *key, int defaultValue)
108{
109 if (parameters.contains(akey: QString::fromLatin1(str: key))) {
110 bool ok;
111 int version = parameters.value(akey: QString::fromLatin1(str: key)).toInt(ok: &ok);
112
113 if (ok)
114 return version;
115 }
116 return defaultValue;
117}
118
119void QContactManagerData::createEngine(const QString &managerName, const QMap<QString, QString> &parameters)
120{
121 m_engine = 0;
122
123 QString builtManagerName = managerName.isEmpty() ? QContactManager::availableManagers().value(i: 0) : managerName;
124 int implementationVersion = parameterValue(parameters, QTCONTACTS_IMPLEMENTATION_VERSION_NAME, defaultValue: -1);
125
126 bool found = false;
127 bool loadedDynamic = false;
128
129 /* First check static factories */
130 loadStaticFactories();
131
132 /* See if we got a fast hit */
133 QList<QContactManagerEngineFactory*> factories = m_engines.values(akey: builtManagerName);
134 m_lastError = QContactManager::NoError;
135
136 while (!found) {
137 foreach (QContactManagerEngineFactory* f, factories) {
138 QList<int> versions = f->supportedImplementationVersions();
139 if (implementationVersion == -1 ||//no given implementation version required
140 versions.isEmpty() || //the manager engine factory does not report any version
141 versions.contains(t: implementationVersion)) {
142 m_engine = f->engine(parameters, error: &m_lastError);
143 if (!m_engine) {
144 qWarning(msg: "Creation of %s engine failed.", qPrintable(managerName));
145 } else {
146 found = true;
147 break;
148 }
149 }
150 }
151
152 // Break if found or if this is the second time through
153 if (loadedDynamic || found)
154 break;
155
156 // otherwise load dynamic factories and reloop
157 loadFactoriesMetadata();
158 if (!m_metaData.isEmpty()) {
159 QFactoryLoader *l = loader();
160 foreach (const QJsonObject &metaDataObject, m_metaData) {
161 if (metaDataObject.value(QStringLiteral("MetaData")).toObject().value(QStringLiteral("Keys")).toArray().at(i: 0).toString() ==
162 builtManagerName) {
163 QContactManagerEngineFactory *managerFactory = qobject_cast<QContactManagerEngineFactory *>(object: l->instance(index: m_metaData.indexOf(t: metaDataObject)));
164 QContactActionManagerPlugin *actionFactory = qobject_cast<QContactActionManagerPlugin *>(object: l->instance(index: m_metaData.indexOf(t: metaDataObject)));
165 m_engines.insertMulti(key: builtManagerName, value: managerFactory);
166 m_actionManagers.append(t: actionFactory);
167 }
168 }
169 }
170 factories = m_engines.values(akey: builtManagerName);
171 loadedDynamic = true;
172 }
173
174 // XXX remove this
175 // the engine factory could lie to us, so check the real implementation version
176 if (m_engine && (implementationVersion != -1 && m_engine->managerVersion() != implementationVersion)) {
177 m_lastError = QContactManager::VersionMismatchError;
178 m_engine = 0;
179 }
180
181 if (!m_engine) {
182 if (m_lastError == QContactManager::NoError)
183 m_lastError = QContactManager::DoesNotExistError;
184 m_engine = new QContactInvalidEngine();
185 }
186}
187
188void QContactManagerData::loadStaticFactories()
189{
190 if (!m_discoveredStatic) {
191#if !defined QT_NO_DEBUG
192 const bool showDebug = qgetenv(varName: "QT_DEBUG_PLUGINS").toInt() > 0;
193#endif
194
195 m_discoveredStatic = true;
196
197 /* Clean stuff up at the end */
198 qAddPostRoutine(qContactsCleanEngines);
199
200 /* Loop over all the static plugins */
201 QObjectList staticPlugins = QPluginLoader::staticInstances();
202 for (int i=0; i < staticPlugins.count(); i++ ){
203 QContactManagerEngineFactory *f = qobject_cast<QContactManagerEngineFactory*>(object: staticPlugins.at(i));
204 if (f) {
205 QString name = f->managerName();
206
207#if !defined QT_NO_DEBUG
208 if (showDebug)
209 qDebug() << "Static: found an engine plugin" << f << "with name" << name;
210#endif
211 if (name != QStringLiteral("invalid") && !name.isEmpty()) {
212 // we also need to ensure that we haven't already loaded this factory.
213 if (m_engines.keys().contains(t: name)) {
214 qWarning(msg: "Static contacts plugin %s has the same name as a currently loaded plugin; ignored", qPrintable(name));
215 } else {
216 m_engines.insertMulti(key: name, value: f);
217 }
218 } else {
219 qWarning(msg: "Static contacts plugin with reserved name %s ignored", qPrintable(name));
220 }
221 }
222 }
223 }
224}
225
226
227/* Plugin loader */
228void QContactManagerData::loadFactoriesMetadata()
229{
230#if !defined QT_NO_DEBUG
231 const bool showDebug = qgetenv(varName: "QT_DEBUG_PLUGINS").toInt() > 0;
232#endif
233
234 // Always do this..
235 loadStaticFactories();
236
237 QFactoryLoader *l = loader();
238 QList<QJsonObject> metaData = l->metaData();
239 m_metaData = metaData;
240 if (m_metaData != m_pluginPaths) {
241 m_pluginPaths = m_metaData;
242 QString currentManagerName;
243 /* Now discover the dynamic plugins */
244 foreach (const QJsonObject &metaDataObject, metaData) {
245 currentManagerName = metaDataObject.value(QStringLiteral("MetaData")).toObject().value(QStringLiteral("Keys")).toArray().at(i: 0).toString();
246
247#if !defined QT_NO_DEBUG
248 if (showDebug)
249 qDebug() << "Loading metadata of plugin " << currentManagerName;
250#endif
251
252 if (!currentManagerName.isEmpty())
253 m_managerNames << currentManagerName;
254 }
255 }
256}
257
258// Observer stuff
259
260void QContactManagerData::registerObserver(QContactManager *manager, QContactObserver *observer)
261{
262 if (!manager)
263 return;
264
265 QContactManagerData* d = QContactManagerData::get(manager);
266
267 d->m_observerForContact.insert(akey: observer->contactId(), avalue: observer);
268
269 // If this is the first observer, connect to the engine too
270 if (d->m_observerForContact.size() == 1) {
271 // This takes advantage of the manager connectNotify code
272 QObject::connect(sender: manager, SIGNAL(contactsChanged(QList<QContactId>,QList<QContactDetail::DetailType>)),
273 receiver: manager, SLOT(_q_contactsUpdated(QList<QContactId>,QList<QContactDetail::DetailType>)));
274 QObject::connect(sender: manager, SIGNAL(contactsRemoved(QList<QContactId>)),
275 receiver: manager, SLOT(_q_contactsDeleted(QList<QContactId>)));
276 }
277}
278
279void QContactManagerData::unregisterObserver(QContactManager *manager, QContactObserver *observer)
280{
281 Q_ASSERT(manager);
282
283 QContactManagerData* d = QContactManagerData::get(manager);
284
285 QContactId key = d->m_observerForContact.key(avalue: observer);
286 if (!key.isNull()) {
287 d->m_observerForContact.remove(key, value: observer);
288
289 // If there are now no more observers, disconnect from the engine
290 if (d->m_observerForContact.size() == 0) {
291 // This takes advantage of the manager disconnectNotify code
292 QObject::disconnect(sender: manager, SIGNAL(contactsChanged(QList<QContactId>,QList<QContactDetail::DetailType>)),
293 receiver: manager, SLOT(_q_contactsUpdated(QList<QContactId>,QList<QContactDetail::DetailType>)));
294 QObject::disconnect(sender: manager, SIGNAL(contactsRemoved(QList<QContactId>)),
295 receiver: manager, SLOT(_q_contactsDeleted(QList<QContactId>)));
296 }
297 }
298}
299
300void QContactManagerData::_q_contactsUpdated(const QList<QContactId> &ids, const QList<QContactDetail::DetailType> &typesChanged)
301{
302 foreach (const QContactId &id, ids) {
303 QList<QContactObserver*> observers = m_observerForContact.values(akey: id);
304 foreach (QContactObserver* observer, observers) {
305 QMetaObject::invokeMethod(obj: observer, member: "contactChanged", Q_ARG(QList<QContactDetail::DetailType>, typesChanged));
306 }
307 }
308}
309
310void QContactManagerData::_q_contactsDeleted(const QList<QContactId> &ids)
311{
312 foreach (const QContactId &id, ids) {
313 QList<QContactObserver*> observers = m_observerForContact.values(akey: id);
314 foreach (QContactObserver* observer, observers) {
315 QMetaObject::invokeMethod(obj: observer, member: "contactRemoved");
316 }
317 }
318}
319
320// trampolines for private classes
321QContactManagerData* QContactManagerData::get(const QContactManager *manager)
322{
323 return manager->d;
324}
325
326QContactManagerEngine* QContactManagerData::engine(const QContactManager *manager)
327{
328 if (manager)
329 return manager->d->m_engine;
330 return 0;
331}
332
333static inline QString escapeParam(const QString &param)
334{
335 QString ret;
336 const int len = param.length();
337 ret.reserve(asize: len + (len >> 3));
338 for (QString::const_iterator it = param.begin(), end = param.end(); it != end; ++it) {
339 switch (it->unicode()) {
340 case ':':
341 ret += QStringLiteral("&#58;");
342 break;
343 case '=':
344 ret += QStringLiteral("&equ;");
345 break;
346 case '&':
347 ret += QStringLiteral("&amp;");
348 break;
349 default:
350 ret += *it;
351 break;
352 }
353 }
354 return ret;
355}
356
357static inline QByteArray escapeColon(const QByteArray &param)
358{
359 QByteArray ret;
360 const int len = param.length();
361 ret.reserve(asize: len + (len >> 3));
362 for (QByteArray::const_iterator it = param.begin(), end = param.end(); it != end; ++it) {
363 switch (*it) {
364 case ':':
365 ret += "&#58;";
366 break;
367 default:
368 ret += *it;
369 break;
370 }
371 }
372 return ret;
373}
374
375static inline QString unescapeParam(const QString &param)
376{
377 QString ret(param);
378 int index = 0;
379 while ((index = ret.indexOf(c: QLatin1Char('&'), from: index)) != -1) {
380 const QString partial(ret.mid(position: index, n: 5));
381 if (partial == QStringLiteral("&#58;"))
382 ret.replace(i: index, len: 5, QStringLiteral(":"));
383 else if (partial == QStringLiteral("&equ;"))
384 ret.replace(i: index, len: 5, QStringLiteral("="));
385 else if (partial == QStringLiteral("&amp;"))
386 ret.replace(i: index, len: 5, QStringLiteral("&"));
387 ++index;
388 }
389 return ret;
390}
391
392static inline QByteArray unescapeColon(const QByteArray &param)
393{
394 QByteArray ret(param);
395 int index = 0;
396 while ((index = ret.indexOf(c: '&', from: index)) != -1) {
397 const QByteArray partial(ret.mid(index, len: 5));
398 if (partial == "&#58;")
399 ret.replace(index, len: 5, s: ":");
400 ++index;
401 }
402 return ret;
403}
404
405/*!
406 Parses the individual components of the given \a uriString and fills the
407 \a managerName, \a params and \a managerUri and \a localId.
408 Returns true if the parts could be parsed successfully, false otherwise.
409*/
410bool QContactManagerData::parseUri(const QString &uriString, QString *managerName, QMap<QString, QString> *params, bool strict)
411{
412 // Format: qtcontacts:<managerid>:<key>=<value>&<key>=<value>
413 // we assume that the prefix, managerid, and params cannot contain `:', `=', or `&'
414 // similarly, that neither param keys nor param values can contain these characters.
415
416 const QStringList colonSplit = uriString.split(sep: QLatin1Char(':'), behavior: QString::KeepEmptyParts);
417 if ((colonSplit.size() != 3) && (strict || colonSplit.size() != 2))
418 return false;
419
420 const QString prefix = colonSplit.at(i: 0);
421 const QString mgrName = colonSplit.at(i: 1);
422 const QString paramString = colonSplit.value(i: 2);
423
424 if (prefix != QStringLiteral("qtcontacts") || mgrName.isEmpty())
425 return false;
426
427 if (!paramString.isEmpty()) {
428 // Now we have to decode each parameter
429 QMap<QString, QString> outParams;
430 const QStringList pairs = paramString.split(sep: QRegExp(QStringLiteral("&(?!(amp;|equ;|#))")), behavior: QString::KeepEmptyParts);
431 for (int i = 0; i < pairs.size(); ++i) {
432 // This should be something like "foo&amp;bar&equ;=grob&amp;"
433 const QStringList pair = pairs.at(i).split(sep: QLatin1Char('='), behavior: QString::KeepEmptyParts);
434 if (pair.size() != 2)
435 return false;
436
437 QString arg = pair.at(i: 0);
438 QString param = pair.at(i: 1);
439
440 if (arg.isEmpty())
441 return false;
442
443 arg = unescapeParam(param: arg);
444 param = unescapeParam(param);
445
446 outParams.insert(akey: arg, avalue: param);
447 }
448
449 if (params)
450 *params = outParams;
451 }
452
453 if (managerName)
454 *managerName = unescapeParam(param: mgrName);
455
456 return true;
457}
458
459/*!
460 Returns an ID string that describes a manager name and parameters with which to instantiate
461 a manager object, from the given \a managerName and \a params.
462 If \a localId is non-null, the generated ID string is suitable for
463 passing to QContactId::fromString().
464*/
465QString QContactManagerData::buildUri(const QString &managerName, const QMap<QString, QString> &params)
466{
467 // Format: qtcontacts:<managerid>:<key>=<value>&<key>=<value>
468 // if the prefix, managerid, param keys, or param values contain `:', `=', or `&',
469 // we escape them to `&#58;', `&equ;', and `&amp;', respectively.
470
471 QString paramString;
472 QMap<QString, QString>::const_iterator it = params.constBegin();
473 for ( ; it != params.constEnd(); ++it) {
474 if (it.key().isEmpty())
475 continue;
476 if (!paramString.isEmpty())
477 paramString += QLatin1Char('&');
478 paramString += escapeParam(param: it.key()) + QLatin1Char('=') + escapeParam(param: it.value());
479 }
480
481 return QStringLiteral("qtcontacts:") + escapeParam(param: managerName) + QLatin1Char(':') + paramString;
482}
483
484/*!
485 Parses the individual components of the given \a idData and fills the
486 \a managerName, \a params, \a managerUri and \a localId.
487 Returns true if the parts could be parsed successfully, false otherwise.
488*/
489bool QContactManagerData::parseIdData(const QByteArray &idData, QString *managerName, QMap<QString, QString> *params, QString *managerUri, QByteArray *localId)
490{
491 // Format: <managerUri>:<localId>
492 int splitIndex = idData.lastIndexOf(c: ':');
493 if (splitIndex == -1)
494 return false;
495
496 const QString uriString(QString::fromUtf8(str: idData.mid(index: 0, len: splitIndex)));
497 if (!parseUri(uriString, managerName, params))
498 return false;
499
500 if (managerUri)
501 *managerUri = uriString;
502 if (localId)
503 *localId = unescapeColon(param: idData.mid(index: splitIndex + 1));
504
505 return true;
506}
507
508/*!
509 Returns an ID string that describes a manager name and parameters with which to instantiate
510 a manager object, from the given \a managerUri.
511 If \a localId is non-null, the generated ID string is suitable for
512 passing to QContactId::fromString().
513*/
514QByteArray QContactManagerData::buildIdData(const QString &managerUri, const QByteArray &localId)
515{
516 // Format: <managerUri>:<localId>
517 // localId cannot contain ':' so it must be escaped
518 QByteArray rv = managerUri.toUtf8();
519 if (!localId.isEmpty())
520 rv.append(c: ':').append(a: escapeColon(param: localId));
521 return rv;
522}
523
524/*!
525 Returns an ID string that describes a manager name and parameters with which to instantiate
526 a manager object, from the given \a managerName and \a params.
527 If \a localId is non-null, the generated ID string is suitable for
528 passing to QContactId::fromString().
529*/
530QByteArray QContactManagerData::buildIdData(const QString &managerName, const QMap<QString, QString> &params, const QByteArray &localId)
531{
532 return buildIdData(managerUri: buildUri(managerName, params), localId);
533}
534
535/*!
536 Returns a cached instance of the manager URI string that matches \a managerUri.
537 This instance should be preferred when constructing ID objects in order to promote
538 data sharing of the URI string.
539*/
540QString QContactManagerData::cachedUri(const QString &managerUri)
541{
542 static QStringList managerUris;
543
544 int index = managerUris.indexOf(t: managerUri);
545 if (index != -1)
546 return managerUris.at(i: index);
547
548 managerUris.append(t: managerUri);
549 return managerUri;
550}
551
552QT_END_NAMESPACE_CONTACTS
553

source code of qtpim/src/contacts/qcontactmanager_p.cpp