/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtContacts module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qcontactmanager.h" #include "qcontactmanager_p.h" #if !defined(QT_NO_DEBUG) #include #endif #include #include #include #include #include #include #include #include "qcontact_p.h" #include "qcontactaction.h" #include "qcontactactiondescriptor.h" #include "qcontactmanagerenginefactory.h" #include "qcontactinvalidbackend_p.h" #include "qcontactspluginsearch_p.h" QT_BEGIN_NAMESPACE_CONTACTS /* Shared QContactManager stuff here, default engine stuff below */ QMultiHash QContactManagerData::m_engines; QSet QContactManagerData::m_aliveEngines; QList QContactManagerData::m_actionManagers; bool QContactManagerData::m_discoveredStatic; QList QContactManagerData::m_pluginPaths; QList QContactManagerData::m_metaData; QStringList QContactManagerData::m_managerNames; Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, (QT_CONTACT_MANAGER_ENGINE_INTERFACE, QLatin1String("/contacts"))) static void qContactsCleanEngines() { // This is complicated by needing to remove any engines before we unload factories // guard pointers as one engine could be parent of another manager and cause doubledelete QList > aliveManagers; foreach(QContactManager* manager, QContactManagerData::m_aliveEngines) { aliveManagers << QPointer(manager); } foreach(QPointer manager, aliveManagers) { if (!manager) { // deleting engine of one manager, could cause deleting next manager in list (aggregation case) continue; } // We don't delete the managers here, we just kill their engines // and replace it with an invalid engine (for safety :/) QContactManagerData* d = QContactManagerData::managerData(manager.data()); delete d->m_engine; d->m_engine = new QContactInvalidEngine(); } QList factories = QContactManagerData::m_engines.values(); for (int i=0; i < factories.count(); i++) { delete factories.at(i); } QContactManagerData::m_engines.clear(); QContactManagerData::m_actionManagers.clear(); QContactManagerData::m_aliveEngines.clear(); QContactManagerData::m_pluginPaths.clear(); QContactManagerData::m_metaData.clear(); QContactManagerData::m_managerNames.clear(); } static int parameterValue(const QMap ¶meters, const char *key, int defaultValue) { if (parameters.contains(QString::fromLatin1(key))) { bool ok; int version = parameters.value(QString::fromLatin1(key)).toInt(&ok); if (ok) return version; } return defaultValue; } void QContactManagerData::createEngine(const QString &managerName, const QMap ¶meters) { m_engine = 0; QString builtManagerName = managerName.isEmpty() ? QContactManager::availableManagers().value(0) : managerName; int implementationVersion = parameterValue(parameters, QTCONTACTS_IMPLEMENTATION_VERSION_NAME, -1); bool found = false; bool loadedDynamic = false; /* First check static factories */ loadStaticFactories(); /* See if we got a fast hit */ QList factories = m_engines.values(builtManagerName); m_lastError = QContactManager::NoError; while (!found) { foreach (QContactManagerEngineFactory* f, factories) { QList versions = f->supportedImplementationVersions(); if (implementationVersion == -1 ||//no given implementation version required versions.isEmpty() || //the manager engine factory does not report any version versions.contains(implementationVersion)) { m_engine = f->engine(parameters, &m_lastError); if (!m_engine) { qWarning("Creation of %s engine failed.", qPrintable(managerName)); } else { found = true; break; } } } // Break if found or if this is the second time through if (loadedDynamic || found) break; // otherwise load dynamic factories and reloop loadFactoriesMetadata(); if (!m_metaData.isEmpty()) { QFactoryLoader *l = loader(); foreach (const QJsonObject &metaDataObject, m_metaData) { if (metaDataObject.value(QStringLiteral("MetaData")).toObject().value(QStringLiteral("Keys")).toArray().at(0).toString() == builtManagerName) { QContactManagerEngineFactory *managerFactory = qobject_cast(l->instance(m_metaData.indexOf(metaDataObject))); QContactActionManagerPlugin *actionFactory = qobject_cast(l->instance(m_metaData.indexOf(metaDataObject))); m_engines.insert(builtManagerName, managerFactory); m_actionManagers.append(actionFactory); } } } factories = m_engines.values(builtManagerName); loadedDynamic = true; } // XXX remove this // the engine factory could lie to us, so check the real implementation version if (m_engine && (implementationVersion != -1 && m_engine->managerVersion() != implementationVersion)) { m_lastError = QContactManager::VersionMismatchError; m_engine = 0; } if (!m_engine) { if (m_lastError == QContactManager::NoError) m_lastError = QContactManager::DoesNotExistError; m_engine = new QContactInvalidEngine(); } } void QContactManagerData::loadStaticFactories() { if (!m_discoveredStatic) { #if !defined QT_NO_DEBUG const bool showDebug = qgetenv("QT_DEBUG_PLUGINS").toInt() > 0; #endif m_discoveredStatic = true; /* Clean stuff up at the end */ qAddPostRoutine(qContactsCleanEngines); /* Loop over all the static plugins */ QObjectList staticPlugins = QPluginLoader::staticInstances(); for (int i=0; i < staticPlugins.count(); i++ ){ QContactManagerEngineFactory *f = qobject_cast(staticPlugins.at(i)); if (f) { QString name = f->managerName(); #if !defined QT_NO_DEBUG if (showDebug) qDebug() << "Static: found an engine plugin" << f << "with name" << name; #endif if (name != QStringLiteral("invalid") && !name.isEmpty()) { // we also need to ensure that we haven't already loaded this factory. if (m_engines.keys().contains(name)) { qWarning("Static contacts plugin %s has the same name as a currently loaded plugin; ignored", qPrintable(name)); } else { m_engines.insert(name, f); } } else { qWarning("Static contacts plugin with reserved name %s ignored", qPrintable(name)); } } } } } /* Plugin loader */ void QContactManagerData::loadFactoriesMetadata() { #if !defined QT_NO_DEBUG const bool showDebug = qgetenv("QT_DEBUG_PLUGINS").toInt() > 0; #endif // Always do this.. loadStaticFactories(); QFactoryLoader *l = loader(); QList metaData = l->metaData(); m_metaData = metaData; if (m_metaData != m_pluginPaths) { m_pluginPaths = m_metaData; QString currentManagerName; /* Now discover the dynamic plugins */ foreach (const QJsonObject &metaDataObject, metaData) { currentManagerName = metaDataObject.value(QStringLiteral("MetaData")).toObject().value(QStringLiteral("Keys")).toArray().at(0).toString(); #if !defined QT_NO_DEBUG if (showDebug) qDebug() << "Loading metadata of plugin " << currentManagerName; #endif if (!currentManagerName.isEmpty()) m_managerNames << currentManagerName; } } } // Observer stuff void QContactManagerData::registerObserver(QContactManager *manager, QContactObserver *observer) { if (!manager) return; QContactManagerData* d = QContactManagerData::get(manager); d->m_observerForContact.insert(observer->contactId(), observer); // If this is the first observer, connect to the engine too if (d->m_observerForContact.size() == 1) { // This takes advantage of the manager connectNotify code QObject::connect(manager, SIGNAL(contactsChanged(QList,QList)), manager, SLOT(_q_contactsUpdated(QList,QList))); QObject::connect(manager, SIGNAL(contactsRemoved(QList)), manager, SLOT(_q_contactsDeleted(QList))); } } void QContactManagerData::unregisterObserver(QContactManager *manager, QContactObserver *observer) { Q_ASSERT(manager); QContactManagerData* d = QContactManagerData::get(manager); QContactId key = d->m_observerForContact.key(observer); if (!key.isNull()) { d->m_observerForContact.remove(key, observer); // If there are now no more observers, disconnect from the engine if (d->m_observerForContact.size() == 0) { // This takes advantage of the manager disconnectNotify code QObject::disconnect(manager, SIGNAL(contactsChanged(QList,QList)), manager, SLOT(_q_contactsUpdated(QList,QList))); QObject::disconnect(manager, SIGNAL(contactsRemoved(QList)), manager, SLOT(_q_contactsDeleted(QList))); } } } void QContactManagerData::_q_contactsUpdated(const QList &ids, const QList &typesChanged) { foreach (const QContactId &id, ids) { QList observers = m_observerForContact.values(id); foreach (QContactObserver* observer, observers) { QMetaObject::invokeMethod(observer, "contactChanged", Q_ARG(QList, typesChanged)); } } } void QContactManagerData::_q_contactsDeleted(const QList &ids) { foreach (const QContactId &id, ids) { QList observers = m_observerForContact.values(id); foreach (QContactObserver* observer, observers) { QMetaObject::invokeMethod(observer, "contactRemoved"); } } } // trampolines for private classes QContactManagerData* QContactManagerData::get(const QContactManager *manager) { return manager->d; } QContactManagerEngine* QContactManagerData::engine(const QContactManager *manager) { if (manager) return manager->d->m_engine; return 0; } static inline QString escapeParam(const QString ¶m) { QString ret; const int len = param.length(); ret.reserve(len + (len >> 3)); for (QString::const_iterator it = param.begin(), end = param.end(); it != end; ++it) { switch (it->unicode()) { case ':': ret += QStringLiteral(":"); break; case '=': ret += QStringLiteral("&equ;"); break; case '&': ret += QStringLiteral("&"); break; default: ret += *it; break; } } return ret; } static inline QByteArray escapeColon(const QByteArray ¶m) { QByteArray ret; const int len = param.length(); ret.reserve(len + (len >> 3)); for (QByteArray::const_iterator it = param.begin(), end = param.end(); it != end; ++it) { switch (*it) { case ':': ret += ":"; break; default: ret += *it; break; } } return ret; } static inline QString unescapeParam(const QString ¶m) { QString ret(param); int index = 0; while ((index = ret.indexOf(QLatin1Char('&'), index)) != -1) { const QString partial(ret.mid(index, 5)); if (partial == QStringLiteral(":")) ret.replace(index, 5, QStringLiteral(":")); else if (partial == QStringLiteral("&equ;")) ret.replace(index, 5, QStringLiteral("=")); else if (partial == QStringLiteral("&")) ret.replace(index, 5, QStringLiteral("&")); ++index; } return ret; } static inline QByteArray unescapeColon(const QByteArray ¶m) { QByteArray ret(param); int index = 0; while ((index = ret.indexOf('&', index)) != -1) { const QByteArray partial(ret.mid(index, 5)); if (partial == ":") ret.replace(index, 5, ":"); ++index; } return ret; } /*! Parses the individual components of the given \a uriString and fills the \a managerName, \a params and \a managerUri and \a localId. Returns true if the parts could be parsed successfully, false otherwise. */ bool QContactManagerData::parseUri(const QString &uriString, QString *managerName, QMap *params, bool strict) { // Format: qtcontacts::=&= // we assume that the prefix, managerid, and params cannot contain `:', `=', or `&' // similarly, that neither param keys nor param values can contain these characters. const QStringList colonSplit = uriString.split(QLatin1Char(':'), Qt::KeepEmptyParts); if ((colonSplit.size() != 3) && (strict || colonSplit.size() != 2)) return false; const QString prefix = colonSplit.at(0); const QString mgrName = colonSplit.at(1); const QString paramString = colonSplit.value(2); if (prefix != QStringLiteral("qtcontacts") || mgrName.isEmpty()) return false; if (!paramString.isEmpty()) { // Now we have to decode each parameter QMap outParams; const QStringList pairs = paramString.split(QRegularExpression(QStringLiteral("&(?!(amp;|equ;|#))")), Qt::KeepEmptyParts); for (int i = 0; i < pairs.size(); ++i) { // This should be something like "foo&bar&equ;=grob&" const QStringList pair = pairs.at(i).split(QLatin1Char('='), Qt::KeepEmptyParts); if (pair.size() != 2) return false; QString arg = pair.at(0); QString param = pair.at(1); if (arg.isEmpty()) return false; arg = unescapeParam(arg); param = unescapeParam(param); outParams.insert(arg, param); } if (params) *params = outParams; } if (managerName) *managerName = unescapeParam(mgrName); return true; } /*! Returns an ID string that describes a manager name and parameters with which to instantiate a manager object, from the given \a managerName and \a params. If \a localId is non-null, the generated ID string is suitable for passing to QContactId::fromString(). */ QString QContactManagerData::buildUri(const QString &managerName, const QMap ¶ms) { // Format: qtcontacts::=&= // if the prefix, managerid, param keys, or param values contain `:', `=', or `&', // we escape them to `:', `&equ;', and `&', respectively. QString paramString; QMap::const_iterator it = params.constBegin(); for ( ; it != params.constEnd(); ++it) { if (it.key().isEmpty()) continue; if (!paramString.isEmpty()) paramString += QLatin1Char('&'); paramString += escapeParam(it.key()) + QLatin1Char('=') + escapeParam(it.value()); } return QStringLiteral("qtcontacts:") + escapeParam(managerName) + QLatin1Char(':') + paramString; } /*! Parses the individual components of the given \a idData and fills the \a managerName, \a params, \a managerUri and \a localId. Returns true if the parts could be parsed successfully, false otherwise. */ bool QContactManagerData::parseIdData(const QByteArray &idData, QString *managerName, QMap *params, QString *managerUri, QByteArray *localId) { // Format: : int splitIndex = idData.lastIndexOf(':'); if (splitIndex == -1) return false; const QString uriString(QString::fromUtf8(idData.mid(0, splitIndex))); if (!parseUri(uriString, managerName, params)) return false; if (managerUri) *managerUri = uriString; if (localId) *localId = unescapeColon(idData.mid(splitIndex + 1)); return true; } /*! Returns an ID string that describes a manager name and parameters with which to instantiate a manager object, from the given \a managerUri. If \a localId is non-null, the generated ID string is suitable for passing to QContactId::fromString(). */ QByteArray QContactManagerData::buildIdData(const QString &managerUri, const QByteArray &localId) { // Format: : // localId cannot contain ':' so it must be escaped QByteArray rv = managerUri.toUtf8(); if (!localId.isEmpty()) rv.append(':').append(escapeColon(localId)); return rv; } /*! Returns an ID string that describes a manager name and parameters with which to instantiate a manager object, from the given \a managerName and \a params. If \a localId is non-null, the generated ID string is suitable for passing to QContactId::fromString(). */ QByteArray QContactManagerData::buildIdData(const QString &managerName, const QMap ¶ms, const QByteArray &localId) { return buildIdData(buildUri(managerName, params), localId); } /*! Returns a cached instance of the manager URI string that matches \a managerUri. This instance should be preferred when constructing ID objects in order to promote data sharing of the URI string. */ QString QContactManagerData::cachedUri(const QString &managerUri) { static QStringList managerUris; int index = managerUris.indexOf(managerUri); if (index != -1) return managerUris.at(index); managerUris.append(managerUri); return managerUri; } QT_END_NAMESPACE_CONTACTS