| 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 | |
| 46 | QT_BEGIN_NAMESPACE_ORGANIZER |
| 47 | |
| 48 | QHash<QString, QOrganizerManagerEngineFactory *> QOrganizerManagerData::m_engines; |
| 49 | bool QOrganizerManagerData::m_discovered; |
| 50 | bool QOrganizerManagerData::m_discoveredStatic; |
| 51 | QStringList QOrganizerManagerData::m_pluginPaths; |
| 52 | |
| 53 | #ifndef QT_NO_LIBRARY |
| 54 | Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, (QT_ORGANIZER_MANAGER_ENGINE_INTERFACE, QLatin1String("/organizer" ))) |
| 55 | #endif |
| 56 | |
| 57 | static 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 | |
| 66 | void QOrganizerManagerData::createEngine(const QString &managerName, const QMap<QString, QString> ¶meters) |
| 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 | |
| 108 | void 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 */ |
| 145 | void 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 | |
| 190 | void QOrganizerManagerData::registerObserver(QOrganizerItemObserver *observer) |
| 191 | { |
| 192 | m_observerForItem.insert(akey: observer->itemId(), avalue: observer); |
| 193 | } |
| 194 | |
| 195 | void 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 | |
| 202 | void 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 | |
| 211 | void 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 | |
| 220 | QOrganizerManagerData *QOrganizerManagerData::get(const QOrganizerManager *manager) |
| 221 | { |
| 222 | return manager->d; |
| 223 | } |
| 224 | |
| 225 | QOrganizerManagerEngine *QOrganizerManagerData::engine(const QOrganizerManager *manager) |
| 226 | { |
| 227 | if (manager) |
| 228 | return manager->d->m_engine; |
| 229 | return 0; |
| 230 | } |
| 231 | |
| 232 | static inline QString escapeParam(const QString ¶m) |
| 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(":" ); |
| 241 | break; |
| 242 | case '=': |
| 243 | ret += QStringLiteral("&equ;" ); |
| 244 | break; |
| 245 | case '&': |
| 246 | ret += QStringLiteral("&" ); |
| 247 | break; |
| 248 | default: |
| 249 | ret += *it; |
| 250 | break; |
| 251 | } |
| 252 | } |
| 253 | return ret; |
| 254 | } |
| 255 | |
| 256 | static inline QByteArray escapeColon(const QByteArray ¶m) |
| 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 += ":" ; |
| 265 | break; |
| 266 | default: |
| 267 | ret += *it; |
| 268 | break; |
| 269 | } |
| 270 | } |
| 271 | return ret; |
| 272 | } |
| 273 | |
| 274 | static inline QString unescapeParam(const QString ¶m) |
| 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(":" )) |
| 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("&" )) |
| 285 | ret.replace(i: index, len: 5, QStringLiteral("&" )); |
| 286 | ++index; |
| 287 | } |
| 288 | return ret; |
| 289 | } |
| 290 | |
| 291 | static inline QByteArray unescapeColon(const QByteArray ¶m) |
| 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 == ":" ) |
| 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 | */ |
| 309 | bool 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&bar&equ;=grob&" |
| 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 | */ |
| 364 | QString QOrganizerManagerData::buildUri(const QString &managerName, const QMap<QString, QString> ¶ms) |
| 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 `:', `&equ;', and `&', 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 | */ |
| 388 | bool 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 | */ |
| 413 | QByteArray 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 | */ |
| 429 | QByteArray QOrganizerManagerData::buildIdData(const QString &managerName, const QMap<QString, QString> ¶ms, 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 | */ |
| 439 | QString 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 | |
| 451 | QT_END_NAMESPACE_ORGANIZER |
| 452 | |