Fixes: Prevent late signal emission from waitForFinished()
[qtcontacts-tracker:qtcontacts-tracker.git] / src / engine / engine.cpp
1 /** This file is part of QtContacts tracker storage plugin
2  **
3  ** Copyright (c) 2009-2011 Nokia Corporation and/or its subsidiary(-ies).
4  **
5  ** Contact:  Nokia Corporation (info@qt.nokia.com)
6  **
7  ** GNU Lesser General Public License Usage
8  ** This file may be used under the terms of the GNU Lesser General Public License
9  ** version 2.1 as published by the Free Software Foundation and appearing in the
10  ** file LICENSE.LGPL included in the packaging of this file.  Please review the
11  ** following information to ensure the GNU Lesser General Public License version
12  ** 2.1 requirements will be met:
13  ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
14  **
15  ** In addition, as a special exception, Nokia gives you certain additional rights.
16  ** These rights are described in the Nokia Qt LGPL Exception version 1.1, included
17  ** in the file LGPL_EXCEPTION.txt in this package.
18  **
19  ** Other Usage
20  ** Alternatively, this file may be used in accordance with the terms and
21  ** conditions contained in a signed written agreement between you and Nokia.
22  **/
23
24 #include "engine.h"
25 #include "engine_p.h"
26
27 #include "contactfetchrequest.h"
28 #include "contactidfetchrequest.h"
29 #include "contactcopyandremoverequest.h"
30 #include "contactunmergerequest.h"
31 #include "queue.h"
32 #include "relationshipfetchrequest.h"
33 #include "relationshipremoverequest.h"
34 #include "relationshipsaverequest.h"
35 #include "detaildefinitionfetchrequest.h"
36 #include "trackerchangelistener.h"
37 #include "tasks.h"
38
39 #include <dao/contactdetail.h>
40 #include <dao/contactdetailschema.h>
41 #include <dao/scalarquerybuilder.h>
42 #include <dao/scalarquerybuilder.h>
43 #include <dao/sparqlresolver.h>
44 #include <dao/support.h>
45
46 #include <lib/contactmergerequest.h>
47 #include <lib/customdetails.h>
48 #include <lib/garbagecollector.h>
49 #include <lib/settings.h>
50 #include <lib/threadutils.h>
51 #include <lib/unmergeimcontactsrequest.h>
52
53 #include <qcontactdetails.h>
54
55 #include <ontologies/nco.h>
56 #include <QElapsedTimer>
57
58 #ifdef ENABLE_CREDENTIALS
59 #include <CredentialsManager>
60 #endif // ENABLE_CREDENTIALS
61
62 ////////////////////////////////////////////////////////////////////////////////////////////////////
63
64 CUBI_USE_NAMESPACE_RESOURCES
65 QTM_USE_NAMESPACE
66
67 ////////////////////////////////////////////////////////////////////////////////////////////////////
68
69 #define REPORT_UNSUPPORTED_FUNCTION(error) do {\
70     qctWarn(QString::fromLatin1("Method not implemented yet: %1"). \
71             arg(QLatin1String(Q_FUNC_INFO))); \
72     qctPropagate(QContactManager::NotSupportedError, error); \
73 } while(0)
74
75 ////////////////////////////////////////////////////////////////////////////////////////////////////
76
77 static QContactManager::Error *const noErrorResult = 0;
78 const QString QContactTrackerEngine::DefaultGraphIri = QLatin1String("urn:uuid:08070f5c-a334-4d19-a8b0-12a3071bfab9");
79 const QString QContactTrackerEngine::DefaultSyncTarget = QContactSyncTarget__SyncTargetAddressBook;
80 const QStringList QContactTrackerEngine::DefaultWeakSyncTargets = QStringList() << QContactSyncTarget__SyncTargetTelepathy;
81 const QString QContactTrackerEngine::MangleAllSyncTargets = QLatin1String("all");
82
83 ////////////////////////////////////////////////////////////////////////////////////////////////////
84
85 // added here as list was not working because of
86 // [warn ] QObject::connect: Cannot queue arguments of type 'QContactAbstractRequest::State'
87 // (Make sure 'QContactAbstractRequest::State' is registered using qRegisterMetaType().)
88 Q_DECLARE_METATYPE(QContactAbstractRequest::State)
89
90 ////////////////////////////////////////////////////////////////////////////////////////////////////
91
92 static QMap<QString, QString>
93 readEnvironment()
94 {
95     const QStringList tokenList(QProcessEnvironment::systemEnvironment().
96                                 value(QLatin1String("QT_CONTACTS_TRACKER")).
97                                 replace(QLatin1Char(' '), QString()).
98                                 split(QLatin1String(";")));
99
100     QMap<QString, QString> parameters;
101
102     foreach(const QString &token, tokenList) {
103         const int eq = token.indexOf(QLatin1Char('='));
104         const QString key((eq >= 0 ? token.left(eq) : token).trimmed());
105         const QString value((eq >= 0 ? token.mid(eq + 1).trimmed() : QString()));
106
107         if (not key.isEmpty()) {
108             parameters.insert(key, value);
109         }
110     }
111
112     return parameters;
113 }
114
115 static void
116 parseParameter(int &result, const QString &key, const QString &text)
117 {
118     bool ok = false;
119     const int value = text.toInt(&ok);
120
121     if (ok) {
122         result = value;
123     } else {
124         qctWarn(QString::fromLatin1("Invalid value for %1 argument: %2").arg(key, text));
125     }
126 }
127
128 QContactTrackerEngineParameters::QContactTrackerEngineParameters(const QMap<QString, QString> &parameters,
129                                                                  const QString& engineName, int engineVersion)
130     : m_engineName(engineName)
131     , m_engineVersion(engineVersion)
132     , m_requestTimeout(QContactTrackerEngine::DefaultRequestTimeout)
133     , m_trackerTimeout(QContactTrackerEngine::DefaultTrackerTimeout)
134     , m_coalescingDelay(QContactTrackerEngine::DefaultCoalescingDelay)
135     , m_concurrencyLevel(QctSettings().concurrencyLevel())
136     , m_batchSize(QContactTrackerEngine::DefaultBatchSize)
137     , m_gcLimit(QContactTrackerEngine::DefaultGCLimit)
138     , m_graphIri(QContactTrackerEngine::DefaultGraphIri)
139     , m_syncTarget(QContactTrackerEngine::DefaultSyncTarget)
140     , m_weakSyncTargets(QContactTrackerEngine::DefaultWeakSyncTargets)
141     , m_guidAlgorithm(0)
142     , m_parameters(parameters)
143     , m_debugFlags(0)
144 {
145     // parse parameters
146     static const QStringList managerParameterNames = QStringList() << QLatin1String("graph-iri");
147     static const QMap<QString, QString> environmentParameters = readEnvironment();
148     m_parameters.unite(environmentParameters);
149
150     QString guidAlgorithmName = QctSettings().guidAlgorithmName();
151     QStringList contactTypes;
152
153     for(QMap<QString, QString>::ConstIterator i = m_parameters.constBegin(); i != m_parameters.constEnd(); ++i) {
154         if (managerParameterNames.contains(i.key())) {
155             m_managerParameters.insert(i.key(), i.value());
156         }
157
158         if (QLatin1String("timeout") == i.key()) {
159             parseParameter(m_requestTimeout, i.key(), i.value());
160             continue;
161         }
162
163         if (QLatin1String("tracker-timeout") == i.key()) {
164             parseParameter(m_trackerTimeout, i.key(), i.value());
165             continue;
166         }
167
168         if (QLatin1String("coalescing-delay") == i.key()) {
169             parseParameter(m_coalescingDelay, i.key(), i.value());
170             continue;
171         }
172
173         if (QLatin1String("writeback") == i.key()) {
174             const QSet<QString> values = i.value().split(QLatin1Char(',')).toSet();
175             const bool all(values.contains(QLatin1String("all")));
176
177             if (all || values.contains(QLatin1String("presence"))) {
178                 foreach(QTrackerContactDetailSchema schema, m_detailSchemas) {
179                     schema.setWriteBackPresence(true);
180                 }
181             }
182
183             continue;
184         }
185
186         if (QLatin1String("debug") == i.key()) {
187             const QSet<QString> values = i.value().split(QLatin1Char(',')).toSet();
188             const bool all(values.contains(QLatin1String("all")));
189
190             if (all || values.contains(QLatin1String("queries"))) {
191                 m_debugFlags |= QContactTrackerEngine::ShowSelects;
192                 m_debugFlags |= QContactTrackerEngine::ShowUpdates;
193             }
194             if (all || values.contains(QLatin1String("selects"))) {
195                 m_debugFlags |= QContactTrackerEngine::ShowSelects;
196             }
197             if (all || values.contains(QLatin1String("updates"))) {
198                 m_debugFlags |= QContactTrackerEngine::ShowUpdates;
199             }
200             if (all || values.contains(QLatin1String("models"))) {
201                 m_debugFlags |= QContactTrackerEngine::ShowModels;
202             }
203             if (all || values.contains(QLatin1String("notes"))) {
204                 m_debugFlags |= QContactTrackerEngine::ShowNotes;
205             }
206             if (all || values.contains(QLatin1String("timing"))) {
207                 m_debugFlags |= QContactTrackerEngine::ShowTiming;
208             }
209             if (all || values.contains(QLatin1String("signals"))) {
210                 m_debugFlags |= QContactTrackerEngine::ShowSignals;
211             }
212             if (all || values.contains(QLatin1String("no-sec"))) {
213                 m_debugFlags |= QContactTrackerEngine::SkipSecurityChecks;
214             }
215             if (all || values.contains(QLatin1String("no-nagging"))) {
216                 m_debugFlags |= QContactTrackerEngine::SkipNagging;
217             }
218
219             continue;
220         }
221
222         if (QLatin1String("contact-types") == i.key()) {
223             contactTypes = i.value().split(QLatin1Char(','));
224             continue;
225         }
226
227         if (QLatin1String("concurrency") == i.key()) {
228             if ((m_concurrencyLevel = i.value().toInt()) < 1) {
229                 m_concurrencyLevel = QctSettings().concurrencyLevel();
230             }
231
232             continue;
233         }
234
235         if (QLatin1String("batch-size") == i.key()) {
236             if ((m_batchSize = i.value().toInt()) < 1) {
237                 m_batchSize = QContactTrackerEngine::DefaultBatchSize;
238             }
239
240             continue;
241         }
242
243         if (QLatin1String("gc-limit") == i.key()) {
244             if ((m_gcLimit = i.value().toInt()) < 1) {
245                 m_gcLimit = QContactTrackerEngine::DefaultGCLimit;
246             }
247
248             continue;
249         }
250
251         if (QLatin1String("graph-iri") == i.key()) {
252             m_graphIri = i.value();
253             continue;
254         }
255
256         if (QLatin1String("guid-algorithm") == i.key()) {
257             guidAlgorithmName = i.value();
258             continue;
259         }
260
261         if (QLatin1String("default-sync-target") == i.key()) {
262             m_syncTarget = i.value();
263             continue;
264         }
265
266         if (QLatin1String("weak-sync-targets") == i.key()) {
267             m_weakSyncTargets = i.value().split(QLatin1String(","), QString::SkipEmptyParts);
268
269             if (m_weakSyncTargets.contains(QContactTrackerEngine::MangleAllSyncTargets)) {
270                 m_weakSyncTargets = QStringList(QContactTrackerEngine::MangleAllSyncTargets);
271             }
272
273             continue;
274         }
275
276         if (QLatin1String("com.nokia.qt.mobility.contacts.implementation.version") == i.key()) {
277             continue;
278         }
279
280         qctWarn(QString::fromLatin1("Unknown parameter: %1").arg(i.key()));
281     }
282
283     // setup detail schemas
284     if (contactTypes.isEmpty() || contactTypes.contains(QContactType::TypeContact, Qt::CaseInsensitive)) {
285         QTrackerPersonContactDetailSchema personContactSchema;
286         m_detailSchemas.insert(QContactType::TypeContact, personContactSchema);
287     }
288
289     if (contactTypes.isEmpty() || contactTypes.contains(QContactType::TypeGroup, Qt::CaseInsensitive)) {
290         QTrackerContactGroupDetailSchema contactGroupSchema;
291         m_detailSchemas.insert(QContactType::TypeGroup, contactGroupSchema);
292     }
293
294     // setup GUID algorithm
295     m_guidAlgorithm = QctGuidAlgorithmFactory::algorithm(guidAlgorithmName);
296
297     if (0 == m_guidAlgorithm) {
298         if (not guidAlgorithmName.isEmpty()) {
299             qctWarn(QString::fromLatin1("Unknown GUID algorithm: %1. Using default algorithm.").
300                     arg(guidAlgorithmName));
301         }
302
303         m_guidAlgorithm = QctGuidAlgorithmFactory::algorithm(QctGuidAlgorithm::Default);
304     }
305 }
306
307 ////////////////////////////////////////////////////////////////////////////////////////////////////
308
309 QContactTrackerEngineData::QContactTrackerEngineData(const QMap<QString, QString> &parameters,
310                                                      const QString &engineName, int engineVersion)
311     : QSharedData()
312     , m_parameters(parameters, engineName, engineVersion)
313     , m_selfContactId(0)
314     , m_changeListener(0) // create on demand
315     , m_requestLifeGuard(QMutex::Recursive)
316     , m_queue(new QctQueue)
317     , m_satisfiedDependencies(QTrackerAbstractRequest::NoDependencies)
318     , m_mandatoryTokensFound(false)
319 {
320     // disable security token checks if requested
321     if (m_parameters.m_debugFlags & QContactTrackerEngine::SkipSecurityChecks) {
322         m_mandatoryTokensFound = true;
323     }
324
325     setupDisplayNameFields();
326 }
327
328 QContactTrackerEngineData::QContactTrackerEngineData(const QContactTrackerEngineData& other)
329     : QSharedData(other)
330     , m_parameters(other.m_parameters)
331     , m_displayNameGeneratorListMap(other.m_displayNameGeneratorListMap)
332     , m_supportedDataTypes(other.m_supportedDataTypes)
333     , m_selfContactId(other.m_selfContactId)
334     , m_changeListener(0) // must create our own when needed
335     , m_requestLifeGuard(QMutex::Recursive)
336     , m_queue(new QctQueue) // the queue is per engine
337     , m_satisfiedDependencies(other.m_satisfiedDependencies)
338     , m_gcQueryId(other.m_gcQueryId)
339     , m_mandatoryTokensFound(other.m_mandatoryTokensFound)
340 {
341 }
342
343 QContactTrackerEngineData::~QContactTrackerEngineData()
344 {
345     delete m_queue;
346 }
347
348 static DisplayLabelGeneratorList
349 makeHighPriorityGeneratorList()
350 {
351     DisplayLabelGeneratorList list;
352     return list;
353 }
354
355 static DisplayLabelGeneratorList
356 makeLowPriorityGeneratorList()
357 {
358     DisplayLabelGeneratorList list;
359
360     list
361         // 1) If CustomLabel is set it dominates all other rules, but detailed name fields (NB#200059)
362         << DisplayLabelGenerator::simpleDetailFieldGenerator(QContactName::DefinitionName,
363                                                              QContactName::FieldCustomLabel)
364
365         // 2a) "any available name information" - nickname
366         << DisplayLabelGenerator::simpleDetailFieldGenerator(QContactNickname::DefinitionName,
367                                                              QContactNickname::FieldNickname)
368
369         // 2b) "any available name information" - middle name
370         << DisplayLabelGenerator::simpleDetailFieldGenerator(QContactName::DefinitionName,
371                                                              QContactName::FieldMiddleName)
372
373         // 3) The company name, e.g. "Nokia"
374         << DisplayLabelGenerator::simpleDetailFieldGenerator(QContactOrganization::DefinitionName,
375                                                              QContactOrganization::FieldName)
376
377         // 4a) "IM username", first the most available IM nickname (e.g "The Dude")
378         << DisplayLabelGenerator::simpleDetailFieldGenerator(QContactGlobalPresence::DefinitionName,
379                                                              QContactGlobalPresence::FieldNickname)
380
381         // 4b) IM address as desparate fallback, this could be a long incomprehensible URI.
382         //    Usually it should be something like "thedude@ovi.com".
383         << DisplayLabelGenerator::simpleDetailFieldGenerator(QContactOnlineAccount::DefinitionName,
384                                                              QContactOnlineAccount::FieldAccountUri)
385
386         // 5) The email address, e.g. "hans.wurst@nokia.com"
387         << DisplayLabelGenerator::simpleDetailFieldGenerator(QContactEmailAddress::DefinitionName,
388                                                              QContactEmailAddress::FieldEmailAddress)
389
390         // 6) The phone number, e.g. "+3581122334455
391         << DisplayLabelGenerator::simpleDetailFieldGenerator(QContactPhoneNumber::DefinitionName,
392                                                                 QContactPhoneNumber::FieldNumber)
393
394         // 7) The contact's homepage URL, e.g. "http://www.nokia.com/"
395         << DisplayLabelGenerator::simpleDetailFieldGenerator(QContactUrl::DefinitionName,
396                                                              QContactUrl::FieldUrl);
397
398     return list;
399 }
400
401 void
402 QContactTrackerEngineData::setupDisplayNameFields()
403 {
404     // TODO Get name display rules from contacts settings, described in UI Spec
405     // v1.1 (section 4.9).
406
407     if (m_displayNameGeneratorListMap.isEmpty()) {
408         const DisplayLabelGeneratorList highPriorityGenerators = makeHighPriorityGeneratorList();
409         const DisplayLabelGeneratorList lowPriorityGenerators = makeLowPriorityGeneratorList();
410
411         // variant with "FirstName LastName"
412         m_displayNameGeneratorListMap[QContactDisplayLabel__FieldOrderFirstName] =
413                 DisplayLabelGeneratorList() << highPriorityGenerators
414                 // First and last name, e.g. "Hans Wurst"
415                 << DisplayLabelGenerator::firstNameLastNameGenerator()
416                 << lowPriorityGenerators;
417
418         // variant with "LastName, FirstName"
419         m_displayNameGeneratorListMap[QContactDisplayLabel__FieldOrderLastName] =
420                 DisplayLabelGeneratorList() << highPriorityGenerators
421                 // Last and first name, e.g. "Wurst, Hans"
422                 << DisplayLabelGenerator::lastNameFirstNameGenerator()
423                 << lowPriorityGenerators;
424
425         // variant for no generators, thus no needed details.
426         // useful for unit tests and for optimizing fetch request performance.
427         m_displayNameGeneratorListMap[QContactDisplayLabel__FieldOrderNone] =
428                 DisplayLabelGeneratorList();
429     }
430 }
431
432 ///////////////////////////////////////////////////////////////////////////////
433 // Synchronous API helpers
434 ///////////////////////////////////////////////////////////////////////////////
435
436 RequestEventLoop::RequestEventLoop(QContactAbstractRequest *request, int timeout)
437     : m_finished(request->isFinished() || request->isCanceled())
438 {
439     // direct connection needed to ensure m_finished is toggled immediately
440     connect(request, SIGNAL(stateChanged(QContactAbstractRequest::State)),
441             this, SLOT(stateChanged(QContactAbstractRequest::State)),
442             Qt::DirectConnection);
443
444     if (timeout > 0) {
445         QTimer::singleShot(timeout, this, SLOT(quit()));
446     }
447 }
448
449 void
450 RequestEventLoop::stateChanged(QContactAbstractRequest::State newState)
451 {
452     switch (newState) {
453     case QContactAbstractRequest::FinishedState:
454     case QContactAbstractRequest::CanceledState:
455         m_finished = true;
456         // Slot was attached with direct connection, therefore quit() must be invoked via auto
457         // connection to ensure resultsAvailable() and stateChanged() reach the request thread's
458         // event loop before this request loop is terminated and waitForFinished() leaves.
459         metaObject()->invokeMethod(this, "quit", Qt::AutoConnection);
460         break;
461     default:
462         break;
463     }
464 }
465
466 ////////////////////////////////////////////////////////////////////////////////////////////////////
467
468 QctTaskWaiter::QctTaskWaiter(QctTask *task, QObject *parent)
469     : QObject(parent)
470     , m_task(task)
471     , m_finished(false)
472 {
473     // Use direct connection to keep the task thread from
474     // deleting the task while we interact with it.
475     connect(task, SIGNAL(finished(QctTask*)),
476             this, SLOT(onTaskFinished()), Qt::DirectConnection);
477     connect(task, SIGNAL(destroyed(QObject*)),
478             this, SLOT(onTaskDestroyed()), Qt::DirectConnection);
479 }
480
481 bool
482 QctTaskWaiter::wait(int timeout)
483 {
484     QCT_SYNCHRONIZED(&m_mutex);
485
486     if (0 == m_task) {
487         return false;
488     }
489
490     if (m_finished) {
491         return true;
492     }
493
494     // while waiting the mutex will get released
495     return m_waitCondition.wait(&m_mutex, (timeout != 0 ? timeout : ULONG_MAX));
496 }
497
498 void
499 QctTaskWaiter::onTaskFinished()
500 {
501     {
502         QCT_SYNCHRONIZED(&m_mutex);
503
504         if (0 != m_task) {
505             // We disconnect make sure that we don't get the signal twice with destroyed
506             m_task->disconnect(this);
507         }
508
509         m_finished = true;
510     }
511
512     m_waitCondition.wakeAll();
513 }
514
515 void
516 QctTaskWaiter::onTaskDestroyed()
517 {
518     {
519         QCT_SYNCHRONIZED(&m_mutex);
520         m_task = 0;
521     }
522
523     m_waitCondition.wakeAll();
524 }
525
526 ////////////////////////////////////////////////////////////////////////////////////////////////////
527 // Instances which manage contact engine instances
528 ////////////////////////////////////////////////////////////////////////////////////////////////////
529
530 QContactTrackerEngine::QContactTrackerEngine(const QMap<QString, QString> &parameters,
531                                              const QString &managerName, int interfaceVersion,
532                                              QObject *parent)
533     : d(new QContactTrackerEngineData(parameters, managerName, interfaceVersion))
534 {
535     // workaround for Qt type system madness
536     qRegisterMetaType<QContactAbstractRequest::State>();
537
538     // workaround for QTMOBILITY-1526
539     if (0 != parent) {
540         setParent(parent);
541     }
542
543     connectSignals();
544     registerGcQuery();
545 }
546
547 QContactTrackerEngine::QContactTrackerEngine(const QContactTrackerEngine& other)
548     : d(other.d)
549 {
550     d.detach();
551     connectSignals();
552 }
553
554 QContactTrackerEngine::~QContactTrackerEngine()
555 {
556     disconnectSignals();
557 }
558
559 QContactTrackerEngine&
560 QContactTrackerEngine::operator=(const QContactTrackerEngine& other)
561 {
562     disconnectSignals();
563
564     d = other.d;
565     d.detach();
566
567     connectSignals();
568
569     return *this;
570 }
571
572 ////////////////////////////////////////////////////////////////////////////////////////////////////
573 // Methods which describe the contact engine's capabilities
574 ////////////////////////////////////////////////////////////////////////////////////////////////////
575
576 QMap<QString, QString>
577 QContactTrackerEngine::managerParameters() const
578 {
579     return d->m_parameters.m_managerParameters;
580 }
581
582 QString
583 QContactTrackerEngine::managerName() const
584 {
585     return d->m_parameters.m_engineName;
586 }
587
588 int
589 QContactTrackerEngine::managerVersion() const
590 {
591     return d->m_parameters.m_engineVersion;
592 }
593
594 bool
595 QContactTrackerEngine::hasFeature(QContactManager::ManagerFeature feature,
596                                   const QString& contactType) const
597 {
598     if (not supportedContactTypes().contains(contactType)) {
599         return false;
600     }
601
602     switch (feature) {
603     case QContactManager::ArbitraryRelationshipTypes:
604     case QContactManager::ActionPreferences:
605     case QContactManager::ChangeLogs:
606     case QContactManager::SelfContact:
607         return true;
608
609     case QContactManager::Groups:
610     case QContactManager::Relationships:
611         return supportedContactTypes().contains(QContactType::TypeGroup);
612
613     default:
614         return false;
615     }
616 }
617
618 bool
619 QContactTrackerEngine::isFilterSupported(const QContactFilter& filter) const
620 {
621     return QTrackerScalarContactQueryBuilder::isFilterSupported(filter);
622 }
623
624 QList<QVariant::Type>
625 QContactTrackerEngine::supportedDataTypes() const
626 {
627     if (d->m_supportedDataTypes.isEmpty()) {
628         QSet<QVariant::Type> typeSet;
629
630         foreach(const QTrackerContactDetailSchema& schema, schemas()) {
631             typeSet += schema.supportedDataTypes();
632         }
633
634         d->m_supportedDataTypes = typeSet.toList();
635     }
636
637     return d->m_supportedDataTypes;
638 }
639
640 QStringList
641 QContactTrackerEngine::supportedContactTypes() const
642 {
643     return schemas().keys();
644 }
645
646 QContactDetailDefinitionMap
647 QContactTrackerEngine::detailDefinitions(const QString& contactType,
648                                          QContactManager::Error* error) const
649 {
650     const QTrackerContactDetailSchemaMap::ConstIterator schema = schemas().find(contactType);
651
652     if (schema == schemas().constEnd()) {
653         qctPropagate(QContactManager::InvalidContactTypeError, error);
654         return QContactDetailDefinitionMap();
655     }
656
657     qctPropagate(QContactManager::NoError, error);
658     return schema->detailDefinitions();
659 }
660
661 QContactDetailDefinition
662 QContactTrackerEngine::detailDefinition(const QString& definitionName,
663                                         const QString& contactType,
664                                         QContactManager::Error* error) const
665 {
666     const QTrackerContactDetailSchemaMap::ConstIterator schema = schemas().find(contactType);
667
668     if (schema == schemas().constEnd()) {
669         qctPropagate(QContactManager::InvalidContactTypeError, error);
670         return QContactDetailDefinition();
671     }
672
673     const QContactDetailDefinitionMap &definitions(schema->detailDefinitions());
674     const QContactDetailDefinitionMap::ConstIterator detail(definitions.find(definitionName));
675
676     if (definitions.constEnd() == detail) {
677         qctPropagate(QContactManager::DoesNotExistError, error);
678         return QContactDetailDefinition();
679     }
680
681     qctPropagate(QContactManager::NoError, error);
682     return detail.value();
683 }
684
685 const QTrackerContactDetailSchemaMap &
686 QContactTrackerEngine::schemas() const
687 {
688     return d->m_parameters.m_detailSchemas;
689 }
690
691 const QTrackerContactDetailSchema &
692 QContactTrackerEngine::schema(const QString& contactType) const
693 {
694     QTrackerContactDetailSchemaMap::ConstIterator schema = schemas().find(contactType);
695
696     if (schema == schemas().constEnd()) {
697         qctFail(QString::fromLatin1("Unexpected contact type %1. Aborting.").arg(contactType));
698         schema = schemas().constBegin();
699     }
700
701     return schema.value();
702 }
703
704 bool
705 QContactTrackerEngine::setSelfContactId(const QContactLocalId&,
706                                         QContactManager::Error* error)
707 {
708     REPORT_UNSUPPORTED_FUNCTION(error);
709     return false;
710 }
711
712 QContactLocalId
713 QContactTrackerEngine::selfContactId(QContactManager::Error* error) const
714 {
715     if (0 == d->m_selfContactId) {
716         QctTrackerIdResolver resolver(QStringList() << nco::default_contact_me::iri());
717
718         if (resolver.lookupAndWait()) {
719             d->m_selfContactId = resolver.trackerIds().first();
720         }
721     }
722
723     qctPropagate(d->m_selfContactId ? QContactManager::NoError
724                                     : QContactManager::DoesNotExistError, error);
725
726     return d->m_selfContactId;
727 }
728
729 const QContactTrackerEngine::DebugFlags &
730 QContactTrackerEngine::debugFlags() const
731 {
732     return d->m_parameters.m_debugFlags;
733 }
734
735 bool
736 QContactTrackerEngine::hasDebugFlag(DebugFlag flag) const
737 {
738     return debugFlags().testFlag(flag);
739 }
740
741 int
742 QContactTrackerEngine::concurrencyLevel() const
743 {
744     return d->m_parameters.m_concurrencyLevel;
745 }
746
747 int
748 QContactTrackerEngine::batchSize() const
749 {
750     return d->m_parameters.m_batchSize;
751 }
752
753 int
754 QContactTrackerEngine::gcLimit() const
755 {
756     return d->m_parameters.m_gcLimit;
757 }
758
759 int
760 QContactTrackerEngine::requestTimeout() const
761 {
762     return d->m_parameters.m_requestTimeout;
763 }
764
765 int
766 QContactTrackerEngine::trackerTimeout() const
767 {
768     return d->m_parameters.m_trackerTimeout;
769 }
770
771 int
772 QContactTrackerEngine::coalescingDelay() const
773 {
774     return d->m_parameters.m_coalescingDelay;
775 }
776
777 const QString &
778 QContactTrackerEngine::graphIri() const
779 {
780     return d->m_parameters.m_graphIri;
781 }
782
783 QctGuidAlgorithm &
784 QContactTrackerEngine::guidAlgorithm() const
785 {
786     return *d->m_parameters.m_guidAlgorithm;
787 }
788
789 const QString &
790 QContactTrackerEngine::syncTarget() const
791 {
792     return d->m_parameters.m_syncTarget;
793 }
794
795 QStringList
796 QContactTrackerEngine::weakSyncTargets() const
797 {
798     return d->m_parameters.m_weakSyncTargets;
799 }
800
801 ////////////////////////////////////////////////////////////////////////////////////////////////////
802 // Synchronous data access methods
803 ////////////////////////////////////////////////////////////////////////////////////////////////////
804
805 QList<QContactLocalId>
806 QContactTrackerEngine::contactIds(const QList<QContactSortOrder>& sortOrders,
807                                   QContactManager::Error* error) const
808 {
809     return contactIds(QContactFilter(), sortOrders, error);
810 }
811
812 QList<QContactLocalId>
813 QContactTrackerEngine::contactIds(const QContactFilter& filter,
814                                   const QList<QContactSortOrder>& sortOrders,
815                                   QContactManager::Error* error) const
816 {
817     QContactLocalIdFetchRequest request;
818
819     request.setFilter(filter);
820     request.setSorting(sortOrders);
821
822     runSyncRequest(&request, error);
823
824     return request.ids();
825 }
826
827 QList<QContact>
828 QContactTrackerEngine::contacts(const QContactFilter& filter,
829                                 const QList<QContactSortOrder>& sortOrders,
830                                 const QContactFetchHint& fetchHint,
831                                 QContactManager::Error* error) const
832 {
833     QContactFetchRequest request;
834
835     request.setFetchHint(fetchHint);
836     request.setFilter(filter);
837     request.setSorting(sortOrders);
838
839     runSyncRequest(&request, error);
840
841     return request.contacts();
842 }
843
844 QContact
845 QContactTrackerEngine::contact(const QContactLocalId& contactId,
846                                const QContactFetchHint& fetchHint,
847                                QContactManager::Error* error) const
848 {
849     static bool warningNotShownYet = true;
850
851     if (warningNotShownYet) {
852         qctWarn(QString::fromLatin1("\n"
853                 "=============================================================================\n"
854                 "WARNING /!\\ - AVOID CALLING THIS FUNCTION FROM PRODUCTION CODE!!!\n"
855                 "=============================================================================\n"
856                 "QContactManager::contact() is blocking on D-Bus roundtrip while accessing\n"
857                 "tracker. Please consider using batch API (QContactManager::contacts()),\n"
858                 "or even better use the asynchronous QContactFetchRequest API, instead of\n"
859                 "fetching contacts one by one.\n"
860                 "\n"
861                 "Please note that reading 100 ids and 100 contact by ids is ~100 times\n"
862                 "slower than reading 100 contacts at once with QContactFetchRequest.\n"
863                 "\n"
864                 "Offending application is %1 [%2].\n"
865                 "=============================================================================").
866                 arg(QCoreApplication::applicationFilePath(),
867                     QString::number(QCoreApplication::applicationPid())));
868
869
870         warningNotShownYet = false;
871     }
872
873     return contactImpl(contactId, fetchHint, error);
874 }
875
876 // Used in tests, removed warning while decided if to provide sync api.
877 // Until then customers are advised to use async.
878 QContact
879 QContactTrackerEngine::contactImpl(const QContactLocalId& contactId,
880                                    const QContactFetchHint& fetchHint,
881                                    QContactManager::Error* error) const
882 {
883     QContactLocalIdFilter idFilter;
884     idFilter.setIds(QList<QContactLocalId>() << contactId);
885
886     QContactFetchRequest request;
887     request.setFetchHint(fetchHint);
888     request.setFilter(idFilter);
889
890     if (not runSyncRequest(&request, error)) {
891         return QContact();
892     }
893
894     QList<QContact> contacts(request.contacts());
895
896     if (contacts.isEmpty()) {
897         qctPropagate(QContactManager::DoesNotExistError, error);
898         return QContact();
899     }
900
901     if (contacts.count() > 1) {
902         qctWarn(QString::fromLatin1("Expected only one contact, but got %1").arg(contacts.count()));
903     }
904
905     return contacts.first();
906 }
907
908 bool
909 QContactTrackerEngine::saveContact(QContact* contact, QContactManager::Error* error)
910 {
911     if (0 == contact) {
912         qctPropagate(QContactManager::BadArgumentError, error);
913         return false;
914     }
915
916     QList<QContact> contactList(QList<QContact>() << *contact);
917     bool success = saveContacts(&contactList, 0, error);
918
919     if (not contactList.isEmpty()) {
920         const QContact &savedContact = contactList.first();
921         setContactDisplayLabel(contact, savedContact.displayLabel());
922         contact->setId(savedContact.id());
923     }
924
925     return success;
926 }
927
928 bool
929 QContactTrackerEngine::saveContacts(QList<QContact>* contacts, ErrorMap* errorMap,
930                                     QContactManager::Error* error)
931 {
932     if (0 == contacts) {
933         qctPropagate(QContactManager::BadArgumentError, error);
934         return false;
935     }
936
937     QContactSaveRequest request;
938     request.setContacts(*contacts);
939
940     const bool hasFinished = runSyncRequest(&request, error);
941     qctPropagate(request.errorMap(), errorMap);
942
943     if (not hasFinished) {
944         return false;
945     }
946
947     const QList<QContact> savedContacts(request.contacts());
948
949     // only update the contact id, all other updates must be fetched
950     for(int i = 0; i < contacts->size(); ++i) {
951         QContact &requestContact = (*contacts)[i];
952         const QContact &savedContact = savedContacts[i];
953         setContactDisplayLabel(&requestContact, savedContact.displayLabel());
954         requestContact.setId(savedContact.id());
955     }
956
957     return QContactManager::NoError == request.error();
958 }
959
960 bool
961 QContactTrackerEngine::removeContact(const QContactLocalId &contactId,
962                                      QContactManager::Error *error)
963 {
964     return removeContacts(QList<QContactLocalId>() << contactId, 0, error);
965 }
966
967 bool
968 QContactTrackerEngine::removeContacts(const QList<QContactLocalId>& contactIds,
969                                       ErrorMap *errorMap, QContactManager::Error *error)
970 {
971     QContactRemoveRequest request;
972
973     request.setContactIds(contactIds);
974
975     runSyncRequest(&request, error);
976
977     qctPropagate(request.errorMap(), errorMap);
978
979     return QContactManager::NoError == request.error();
980 }
981
982 QList<QContactRelationship>
983 QContactTrackerEngine::relationships(const QString &relationshipType,
984                                      const QContactId &participantId, QContactRelationship::Role role,
985                                      QContactManager::Error *error) const
986 {
987     QList<QContactRelationship> result;
988
989     // Mapping of this call to QContactRelationshipFetchRequest is not directly possible
990     // as QContactRelationshipFetchRequest gets set first and second contact but not
991     // just one participantId and a role.
992     //
993     // So in the case that role is QContactRelationship::Either two separate requests need to be done.
994
995     if (role==QContactRelationship::First
996         || role==QContactRelationship::Either) {
997         QContactRelationshipFetchRequest request;
998
999         request.setRelationshipType(relationshipType);
1000         request.setFirst(participantId);
1001
1002         QContactManager::Error fetchError = QContactManager::UnspecifiedError;
1003         runSyncRequest(&request, &fetchError);
1004         qctPropagate(fetchError, error);
1005
1006         if (fetchError != QContactManager::NoError) {
1007             return QList<QContactRelationship>();
1008         }
1009
1010         result += request.relationships();
1011     }
1012
1013     if (role==QContactRelationship::Second
1014         || role==QContactRelationship::Either) {
1015         QContactRelationshipFetchRequest request;
1016
1017         request.setRelationshipType(relationshipType);
1018         request.setSecond(participantId);
1019
1020         QContactManager::Error fetchError = QContactManager::UnspecifiedError;
1021         runSyncRequest(&request, &fetchError);
1022         qctPropagate(fetchError, error);
1023
1024         if (fetchError != QContactManager::NoError) {
1025             return QList<QContactRelationship>();
1026         }
1027
1028         result += request.relationships();
1029     }
1030
1031     return result;
1032 }
1033
1034 bool
1035 QContactTrackerEngine::saveRelationship(QContactRelationship *relationship, QContactManager::Error *error)
1036 {
1037     if (0 == relationship) {
1038         qctPropagate(QContactManager::BadArgumentError, error);
1039         return false;
1040     }
1041
1042     QList<QContactRelationship> relationships =
1043             QList<QContactRelationship>() << *relationship;
1044
1045     return saveRelationships(&relationships, 0, error);
1046 }
1047
1048 bool
1049 QContactTrackerEngine::saveRelationships(QList<QContactRelationship> *relationships,
1050                                          ErrorMap *errorMap, QContactManager::Error *error)
1051 {
1052     if (0 == relationships) {
1053         qctPropagate(QContactManager::BadArgumentError, error);
1054         return false;
1055     }
1056
1057     QContactRelationshipSaveRequest request;
1058     request.setRelationships(*relationships);
1059
1060     runSyncRequest(&request, error);
1061     qctPropagate(request.errorMap(), errorMap);
1062
1063     return QContactManager::NoError == request.error();
1064 }
1065
1066 bool
1067 QContactTrackerEngine::removeRelationship(const QContactRelationship &relationship, QContactManager::Error *error)
1068 {
1069     return removeRelationships(QList<QContactRelationship>() << relationship, 0, error);
1070 }
1071
1072 bool
1073 QContactTrackerEngine::removeRelationships(const QList<QContactRelationship> &relationships,
1074                                            ErrorMap *errorMap, QContactManager::Error *error)
1075 {
1076     QContactRelationshipRemoveRequest request;
1077     request.setRelationships(relationships);
1078
1079     runSyncRequest(&request, error);
1080     qctPropagate(request.errorMap(), errorMap);
1081
1082     return QContactManager::NoError == request.error();
1083 }
1084
1085
1086 /// returns the DisplayNameDetailList for the name order given, if found.
1087 /// Default is the one for QContactDisplayLabel__FirstNameLastNameOrder.
1088 static const DisplayLabelGeneratorList &
1089 findDisplayLabelDetails(const DisplayLabelGeneratorListMap &displayNameGeneratorListMap,
1090                         const QString &nameOrder)
1091 {
1092     DisplayLabelGeneratorListMap::ConstIterator it = displayNameGeneratorListMap.find(nameOrder);
1093
1094     // not found?
1095     if (it == displayNameGeneratorListMap.constEnd()) {
1096         // use default setting
1097         it = displayNameGeneratorListMap.find(QctSettings().nameOrder());
1098     }
1099     // should not happen, but better safe than sorry:
1100     if (it == displayNameGeneratorListMap.constEnd()) {
1101         it = displayNameGeneratorListMap.constBegin();
1102     }
1103
1104     return it.value();
1105 }
1106
1107 QContactFetchHint
1108 QContactTrackerEngine::normalizedFetchHint(QContactFetchHint fetchHint, const QString &nameOrder)
1109 {
1110     QStringList detailDefinitionHint = fetchHint.detailDefinitionsHint();
1111
1112     // Make sure the display name can be synthesized when needed
1113     if (detailDefinitionHint.contains(QContactDisplayLabel::DefinitionName)) {
1114         const DisplayLabelGeneratorList &displayLabelGenerators =
1115             findDisplayLabelDetails(d->m_displayNameGeneratorListMap, nameOrder);
1116
1117         foreach(const DisplayLabelGenerator &generator, displayLabelGenerators) {
1118             if (not detailDefinitionHint.contains(generator.requiredDetailName())) {
1119                 detailDefinitionHint += generator.requiredDetailName();
1120             }
1121         }
1122
1123         fetchHint.setDetailDefinitionsHint(detailDefinitionHint);
1124     }
1125
1126     return fetchHint;
1127 }
1128
1129 void
1130 QContactTrackerEngine::updateDisplayLabel(QContact &contact, const QString &nameOrder) const
1131 {
1132     const QContactDisplayLabel displayLabel = contact.detail<QContactDisplayLabel>();
1133
1134     if (displayLabel.label().isEmpty()) {
1135         setContactDisplayLabel(&contact, createDisplayLabel(contact, nameOrder));
1136     }
1137 }
1138
1139 QString
1140 QContactTrackerEngine::createDisplayLabel(const QContact &contact,
1141                                           const QString &nameOrder) const
1142 {
1143     const DisplayLabelGeneratorList &displayNameGeneratorList =
1144         findDisplayLabelDetails(d->m_displayNameGeneratorListMap, nameOrder);
1145
1146     QString label;
1147
1148     foreach(const DisplayLabelGenerator &generator, displayNameGeneratorList) {
1149         label = generator.createDisplayLabel(contact);
1150
1151         if (not label.isEmpty()) {
1152             break;
1153         }
1154     }
1155
1156     return label;
1157 }
1158
1159 // gives a ranking for the presence state: the higher the value,
1160 // the more "available" is a contact with that presence state
1161 static int
1162 availabilityRank(QContactPresence::PresenceState state)
1163 {
1164     switch(state) {
1165     case QContactPresence::PresenceUnknown:
1166         return 0;
1167     case QContactPresence::PresenceHidden:
1168     case QContactPresence::PresenceOffline:
1169         return 1;
1170     case QContactPresence::PresenceAway:
1171     case QContactPresence::PresenceExtendedAway:
1172         return 2;
1173     case QContactPresence::PresenceBusy:
1174         return 3;
1175     case QContactPresence::PresenceAvailable:
1176         return 4;
1177     }
1178
1179     return availabilityRank(QContactPresence::PresenceUnknown);
1180 }
1181
1182 template <class T> static void
1183 transfer(const T &key, const QContactDetail &source, QContactDetail &target)
1184 {
1185     QVariant value(source.variantValue(key));
1186
1187     if (not value.isNull()) {
1188         target.setValue(key, value);
1189     }
1190 }
1191
1192 void
1193 QContactTrackerEngine::updateGlobalPresence(QContact &contact)
1194 {
1195     const QList<QContactPresence> presenceDetails(contact.details<QContactPresence>());
1196     const QContactPresence *mostAvailable(0);
1197     const QContactPresence *mostRecent(0);
1198
1199     if (not presenceDetails.isEmpty()) {
1200         mostAvailable = mostRecent = &presenceDetails.first();
1201     }
1202
1203     foreach(const QContactPresence &detail, presenceDetails) {
1204         if (availabilityRank(detail.presenceState()) > availabilityRank(mostAvailable->presenceState())) {
1205             if (detail.timestamp() == mostRecent->timestamp()) {
1206                 mostRecent = &detail;
1207             }
1208
1209             mostAvailable = &detail;
1210         }
1211
1212         if (detail.timestamp() > mostRecent->timestamp()) {
1213             mostRecent = &detail;
1214         }
1215     }
1216
1217     QContactGlobalPresence global(contact.detail<QContactGlobalPresence>());
1218
1219     if (mostRecent && mostAvailable) {
1220         QSet<QString> linkedDetails;
1221         linkedDetails << mostRecent->detailUri();
1222         linkedDetails << mostAvailable->detailUri();
1223         global.setLinkedDetailUris(linkedDetails.toList());
1224
1225         transfer(QContactGlobalPresence::FieldNickname, *mostRecent, global);
1226         transfer(QContactGlobalPresence::FieldTimestamp, *mostRecent, global);
1227         transfer(QContactGlobalPresence::FieldCustomMessage, *mostRecent, global);
1228
1229         transfer(QContactGlobalPresence::FieldPresenceState, *mostAvailable, global);
1230         transfer(QContactGlobalPresence::FieldPresenceStateText, *mostAvailable, global);
1231         transfer(QContactGlobalPresence::FieldPresenceStateImageUrl, *mostAvailable, global);
1232
1233         contact.saveDetail(&global);
1234     } else {
1235         contact.removeDetail(&global);
1236     }
1237 }
1238
1239 typedef QPair<QContactAvatar, int> AvatarWithAvailability;
1240
1241 inline bool
1242 operator<(const AvatarWithAvailability &a, const AvatarWithAvailability &b)
1243 {
1244     // the lower the availability rank, the more significant the detail
1245     return a.second > b.second;
1246 }
1247
1248 void
1249 QContactTrackerEngine::updateAvatar(QContact &contact)
1250 {
1251     QContactPersonalAvatar personalAvatar = contact.detail<QContactPersonalAvatar>();
1252     const QList<QContactOnlineAvatar> currentOnlineAvatars = contact.details<QContactOnlineAvatar>();
1253
1254     // Function was triggered by an update of nco:photo
1255     if (not personalAvatar.isEmpty()) {
1256         QContactAvatar photoAvatar = personalAvatar.toAvatar();
1257
1258         // If there was already a nco:photo (no linked uri), remove it
1259         contact.removeDetail(&personalAvatar);
1260
1261         // nco:photo always goes first, so that QContact::detail<QContactAvatar> returns it
1262         contact.saveDetail(&photoAvatar);
1263     }
1264
1265     // Function was triggered by an update of a IMAddress' imAvatar
1266     if (not currentOnlineAvatars.empty()) {
1267         // find all available presence details
1268         QMap<QString, QContactPresence> presenceDetails;
1269
1270         foreach(const QContactPresence &detail, contact.details<QContactPresence>()) {
1271             const QString accountPath = parsePresenceIri(detail.detailUri());
1272
1273             if (not accountPath.isEmpty()) {
1274                 presenceDetails.insert(accountPath, detail);
1275             }
1276         }
1277
1278         // store online avatars with their availablity
1279         QList<AvatarWithAvailability> avatarsWithAvailability;
1280
1281         foreach(QContactOnlineAvatar avatar, currentOnlineAvatars) {
1282             contact.removeDetail(&avatar);
1283
1284             // FIXME: properly define schema for detailUri and linkedDetailUri
1285             QStringList linkedDetailUris = avatar.linkedDetailUris();
1286
1287             if (linkedDetailUris.isEmpty()) {
1288                 linkedDetailUris += QString();
1289             }
1290
1291             foreach(const QString &uri, linkedDetailUris) {
1292                 const QString accountPath = not uri.isEmpty()
1293                         ? parseTelepathyIri(uri)
1294                         : QString();
1295
1296                 int availability = availabilityRank(QContactPresence::PresenceUnknown);
1297
1298                 if (not accountPath.isEmpty()) {
1299                     const QContactPresence presence = presenceDetails.value(accountPath);
1300                     availability = availabilityRank(presence.presenceState());
1301                 } else {
1302                     qctWarn("QTrackerContactOnlineAvatar should always be linked with an online account");
1303                 }
1304
1305                 avatarsWithAvailability += qMakePair(avatar.toAvatar(), availability);
1306             }
1307         }
1308
1309         // sort avatars by their availablity
1310         qSort(avatarsWithAvailability);
1311
1312         // add regular avatar details to contact
1313         for(int i = 0; i < avatarsWithAvailability.count(); ++i) {
1314             contact.saveDetail(&avatarsWithAvailability[i].first);
1315         }
1316     }
1317 }
1318
1319 bool
1320 QContactTrackerEngine::isWeakSyncTarget(const QString &syncTarget) const
1321 {
1322     if (d->m_parameters.m_weakSyncTargets.contains(syncTarget)) {
1323         return true;
1324     }
1325
1326     return (d->m_parameters.m_weakSyncTargets.count() == 1 &&
1327             d->m_parameters.m_weakSyncTargets.first() == MangleAllSyncTargets);
1328 }
1329
1330 QString
1331 QContactTrackerEngine::gcQueryId() const
1332 {
1333     return d->m_gcQueryId;
1334 }
1335
1336 // TODO: we could cache the return value in this function
1337 QString
1338 QContactTrackerEngine::cleanupQueryString() const
1339 {
1340     // Notice FILTER in WHERE clause - garbage collector needs to verify parenting outside this graph too.
1341     // reason - IMAccount hasIMAddress in contactsd private graph so no need to delete it. The same could apply
1342     // to any other property - it could have been added to parent in some other graph.
1343     // Camera geotagging creates addresses outside qct graph and have no "parent" contact pointing
1344     // to them through nco:hasAddress
1345     static const QString obsoleteResourcesPrefix = QLatin1String
1346             ("\n"
1347              "DELETE\n"
1348              "{\n"
1349              "  GRAPH <%1>\n"
1350              "  {\n"
1351              "    ?resource a <%2> .\n"
1352              "  }\n"
1353              "}\n"
1354              "WHERE\n"
1355              "{\n"
1356              "  GRAPH <%1>\n"
1357              "  {\n"
1358              "    ?resource a <%3> .\n"
1359              "  }\n");
1360     static const QString imAccountStopPattern = QLatin1String
1361             ("    FILTER(NOT EXISTS { ?resource a nco:IMAccount }) .\n");
1362     static const QString obsoleteResourcesPattern = QLatin1String
1363             ("    FILTER(NOT EXISTS { ?parent <%1> ?resource }) .\n");
1364     static const QString obsoleteResourcesSuffix = QLatin1String
1365             ("}\n");
1366
1367     QString queryString;
1368
1369 #ifndef QT_NO_DEBUG
1370     queryString += QLatin1String
1371             ("\n"
1372              "#--------------------------------------.\n"
1373              "# Drop drop obsolete custom properties |\n"
1374              "#--------------------------------------'\n");
1375 #endif // QT_NO_DEBUG
1376
1377     // collect obsolete resource types and their predicates
1378     typedef QMap<QString, bool> PredicateMap;
1379     typedef QMap<QString, PredicateMap> ResourceTypePredicateMap;
1380     ResourceTypePredicateMap obsoleteResources;
1381
1382     foreach(const QTrackerContactDetailSchema &schema, schemas()) {
1383         foreach(const QTrackerContactDetail &detail, schema.details()) {
1384             foreach(const PropertyInfoList &chain, detail.possessedChains()) {
1385                 const PropertyInfoBase &pi = chain.last();
1386
1387                 if (pi.rangeIri() == rdfs::Resource::iri()) {
1388                     qctWarn(QString::fromLatin1("Not cleaning up obsolete resources for %1 property"
1389                                                 "since the property's range is too generic (%2).").
1390                             arg(qctIriAlias(pi.iri()), qctIriAlias(pi.rangeIri())));
1391                     continue;
1392                 }
1393
1394                 obsoleteResources[pi.rangeIri()].insert(pi.iri(), true);
1395             }
1396         }
1397     }
1398
1399     // glue everything together
1400     queryString += obsoleteResourcesPrefix.arg(graphIri(),
1401                                                nao::Property::iri(),
1402                                                nao::Property::iri());
1403     queryString += obsoleteResourcesPattern.arg(nao::hasProperty::iri());
1404     queryString += obsoleteResourcesSuffix;
1405
1406     for(ResourceTypePredicateMap::ConstIterator
1407         t = obsoleteResources.constBegin(); t != obsoleteResources.constEnd(); ++t) {
1408         queryString += obsoleteResourcesPrefix.arg(graphIri(),
1409                                                    rdfs::Resource::iri(),
1410                                                    t.key());
1411
1412         if (nco::IMAddress::iri() == t.key()) {
1413             // FIXME: Remove this workaround for NB#206404 - Saving a contact using
1414             // qtcontacts-tracker causes nco:IMAccount to be removed.
1415             queryString += imAccountStopPattern;
1416         }
1417
1418         foreach(const QString &predicate, t.value().keys()) {
1419             queryString += obsoleteResourcesPattern.arg(predicate);
1420         }
1421
1422         queryString += obsoleteResourcesSuffix;
1423     }
1424
1425     return queryString;
1426 }
1427
1428 ////////////////////////////////////////////////////////////////////////////////////////////////////
1429 // Asynchronous data access methods
1430 ////////////////////////////////////////////////////////////////////////////////////////////////////
1431
1432 bool
1433 QContactTrackerEngine::checkSecurityTokens(QContactAbstractRequest *request)
1434 {
1435     // Plugin users often fail to provide all required security tokens.
1436     // Therefore we print warnings to educate them. If the security checks
1437     // should cause trouble they can be temporarly disabled by exporting
1438     // QT_CONTACTS_TRACKER="debug=no-sec".
1439
1440     if (d->m_mandatoryTokensFound) {
1441         return true;
1442     }
1443
1444     QStringList theGateKeepersBlameList;
1445     QStringList missingOptionalTokens;
1446
1447 #ifdef ENABLE_CREDENTIALS
1448
1449     static const QStringList requiredSecurityTokens =
1450             QStringList() << QLatin1String("TrackerReadAccess")
1451                           << QLatin1String("TrackerWriteAccess");
1452
1453     static const QStringList optionalSecurityTokens =
1454             QStringList() << QLatin1String("GRP::metadata-users");
1455
1456     foreach(const QString &credential, requiredSecurityTokens) {
1457         QString errorMessage;
1458
1459         if (not MssfQt::CredentialsManager::hasProcessCredential(0, credential, &errorMessage)) {
1460             theGateKeepersBlameList += credential;
1461             qctWarn(errorMessage);
1462         }
1463     }
1464
1465     foreach(const QString &credential, optionalSecurityTokens) {
1466         QString errorMessage;
1467
1468         if (not MssfQt::CredentialsManager::hasProcessCredential(0, credential, &errorMessage)) {
1469             missingOptionalTokens += credential;
1470             qctWarn(errorMessage);
1471         }
1472     }
1473
1474 #endif // ENABLE_CREDENTIALS
1475
1476     if (not theGateKeepersBlameList.isEmpty()) {
1477         qctWarn(QString::fromLatin1("\n"
1478                  "=============================================================================\n"
1479                  "WARNING /!\\ - MANDATORY SECURITY TOKENS ARE MISSIN\n"
1480                  "=============================================================================\n"
1481                  "Rejecting %2.\n"
1482                  "Please add an AEGIS manifest to your package requesting the following\n"
1483                  "security tokens: %1 for %3 [%4].\n"
1484                  "=============================================================================").
1485                 arg(theGateKeepersBlameList.join(QLatin1String(", ")),
1486                     QLatin1String(request->metaObject()->className()),
1487                     QCoreApplication::applicationFilePath(),
1488                     QString::number(QCoreApplication::applicationPid())));
1489
1490         return false;
1491     }
1492
1493     if (not missingOptionalTokens.isEmpty()) {
1494         qctWarn(QString::fromLatin1("\n"
1495                  "=============================================================================\n"
1496                  "WARNING /!\\ - OPTIONAL SECURITY TOKENS ARE MISSING\n"
1497                  "=============================================================================\n"
1498                  "Full functionality like tracker direct access is not available.\n"
1499                  "Please add an AEGIS manifest to your package requesting the following\n"
1500                  "security tokens: %1 for %2 [%3].\n"
1501                  "=============================================================================").
1502                 arg(missingOptionalTokens.join(QLatin1String(", ")),
1503                     QCoreApplication::applicationFilePath(),
1504                     QString::number(QCoreApplication::applicationPid())));
1505     }
1506
1507     d->m_mandatoryTokensFound = true;
1508
1509     return true;
1510 }
1511
1512 QctTask*
1513 QContactTrackerEngine::startRequestImpl(QContactAbstractRequest *request)
1514 {
1515     QCT_SYNCHRONIZED(&d->m_requestLifeGuard);
1516
1517     QScopedPointer<QctRequestTask> task(new QctRequestTask(this, request));
1518
1519     // Make sure mandatory tracker:ids have been resolved.
1520     if (task->dependencies().testFlag(QTrackerAbstractRequest::ResourceCache) &&
1521         not d->m_satisfiedDependencies.testFlag(QTrackerAbstractRequest::ResourceCache)) {
1522         d->m_queue->enqueue(new QctResourceCacheTask(schemas(), this));
1523         d->m_satisfiedDependencies |= QTrackerAbstractRequest::ResourceCache;
1524     }
1525
1526     if (task->dependencies().testFlag(QTrackerAbstractRequest::GuidAlgorithm) &&
1527         not d->m_satisfiedDependencies.testFlag(QTrackerAbstractRequest::GuidAlgorithm)) {
1528         d->m_queue->enqueue(new QctGuidAlgorithmTask(this, this));
1529         d->m_satisfiedDependencies |= QTrackerAbstractRequest::GuidAlgorithm;
1530     }
1531
1532     // the contacts engine must bring requests into Activate state as soon as it is dealing with them
1533     // the (secret) state machine only permits this transitions Inactive > Active > Canceled/Finished
1534     // because in case of an error the state can go to Canceled/Finished, set to Active now
1535     // before starting the worker
1536     updateRequestState(request, QContactAbstractRequest::ActiveState);
1537
1538     d->m_queue->enqueue(task.data());
1539
1540     return task.take();
1541 }
1542
1543 bool
1544 QContactTrackerEngine::startRequest(QContactAbstractRequest *request)
1545 {
1546     return (0 != startRequestImpl(request));
1547 }
1548
1549 QTrackerAbstractRequest *
1550 QContactTrackerEngine::createRequestWorker(QContactAbstractRequest *request)
1551 {
1552     // about if mandatory security tokens are missing
1553     if (not checkSecurityTokens(request)) {
1554         return 0;
1555     }
1556
1557     // ensure old worker got destroyed when request gets reused
1558     requestDestroyed(request);
1559
1560     QTrackerAbstractRequest *worker = 0;
1561     QElapsedTimer t; t.start();
1562
1563     // choose proper request implementation
1564     switch(request->type())
1565     {
1566     case QContactAbstractRequest::ContactFetchRequest:
1567         worker = new QTrackerContactFetchRequest(request, this);
1568         break;
1569
1570     case QContactAbstractRequest::ContactLocalIdFetchRequest:
1571         worker = new QTrackerContactIdFetchRequest(request, this);
1572         break;
1573
1574     case QContactAbstractRequest::ContactRemoveRequest:
1575         if (0 == qobject_cast<QctContactMergeRequest *>(request)) {
1576             worker = new QTrackerContactRemoveRequest(request, this);
1577         } else {
1578             worker = new QTrackerContactCopyAndRemoveRequest(request, this);
1579         }
1580         break;
1581
1582     case QContactAbstractRequest::ContactSaveRequest:
1583         if (0 == qobject_cast< QctUnmergeIMContactsRequest *> (request)) {
1584             worker = new QTrackerContactSaveRequest(request, this);
1585         } else {
1586             worker = new QTrackerContactSaveOrUnmergeRequest(request, this);
1587         }
1588         break;
1589
1590     case QContactAbstractRequest::RelationshipFetchRequest:
1591         worker = new QTrackerRelationshipFetchRequest(request, this);
1592         break;
1593
1594     case QContactAbstractRequest::RelationshipRemoveRequest:
1595         worker = new QTrackerRelationshipRemoveRequest(request, this);
1596         break;
1597
1598     case QContactAbstractRequest::RelationshipSaveRequest:
1599         worker = new QTrackerRelationshipSaveRequest(request, this);
1600         break;
1601
1602     case QContactAbstractRequest::DetailDefinitionFetchRequest:
1603         worker = new QTrackerDetailDefinitionFetchRequest(request, this);
1604         break;
1605
1606     case QContactAbstractRequest::InvalidRequest:
1607     case QContactAbstractRequest::DetailDefinitionRemoveRequest:
1608     case QContactAbstractRequest::DetailDefinitionSaveRequest:
1609     case QContactAbstractRequest::ContactFetchByIdRequest:
1610         break;
1611     }
1612
1613     if (0 == worker) {
1614         qctWarn(QString::fromLatin1("Unsupported request type: %1").
1615                 arg(QLatin1String(request->metaObject()->className())));
1616         return 0;
1617     }
1618
1619     if (hasDebugFlag(ShowNotes)) {
1620         qDebug() << Q_FUNC_INFO << "time elapsed while constructing request workers:"<< request << t.elapsed();
1621     }
1622
1623     if (hasDebugFlag(ShowNotes)) {
1624         qDebug() << Q_FUNC_INFO << "running" << worker->metaObject()->className();
1625     }
1626
1627     // XXX The unit tests directly access the engine. Therefore requests created by our unit
1628     // tests don't have a manager attached. This prevents ~QContactAbstractRequest() to call our
1629     // engine's requestDestroyed(QContactAbstractRequest*) method, which results in memory leaks
1630     // within our our unit tests. To prevent those leaks we watch the request's destroyed()
1631     // signal and forward those signals to requestDestroyed(QContactAbstractRequest*) when
1632     // a request without contact manager is found.
1633     //
1634     // XXX This all of course is an ugly workaround. We probably should change the unit tests
1635     // to create use QContactManager accessing the engine via a static plugin.
1636     if (0 == request->manager()) {
1637         connect(request, SIGNAL(destroyed(QObject*)),
1638                 this, SLOT(onRequestDestroyed(QObject*)), Qt::DirectConnection);
1639     }
1640
1641     // track original request for this worker
1642     QCT_SYNCHRONIZED_WRITE(&d->m_tableLock);
1643
1644     d->m_workersByRequest.insert(request, worker);
1645     d->m_requestsByWorker.insert(worker, request);
1646
1647     return worker;
1648 }
1649
1650 QctRequestLocker
1651 QContactTrackerEngine::request(const QTrackerAbstractRequest *worker) const
1652 {
1653     QScopedPointer<QMutexLocker> locker(new QMutexLocker(&d->m_requestLifeGuard));
1654     QCT_SYNCHRONIZED_READ(&d->m_tableLock);
1655     return QctRequestLocker(locker.take(), d->m_requestsByWorker.value(worker));
1656 }
1657
1658 void
1659 QContactTrackerEngine::connectSignals()
1660 {
1661     if (d->m_changeListener) {
1662         connect(d->m_changeListener,
1663                 SIGNAL(contactsAdded(QList<QContactLocalId>)),
1664                 SIGNAL(contactsAdded(QList<QContactLocalId>)));
1665         connect(d->m_changeListener,
1666                 SIGNAL(contactsChanged(QList<QContactLocalId>)),
1667                 SIGNAL(contactsChanged(QList<QContactLocalId>)));
1668         connect(d->m_changeListener,
1669                 SIGNAL(contactsRemoved(QList<QContactLocalId>)),
1670                 SIGNAL(contactsRemoved(QList<QContactLocalId>)));
1671         connect(d->m_changeListener,
1672                 SIGNAL(relationshipsAdded(QList<QContactLocalId>)),
1673                 SIGNAL(relationshipsAdded(QList<QContactLocalId>)));
1674         connect(d->m_changeListener,
1675                 SIGNAL(relationshipsRemoved(QList<QContactLocalId>)),
1676                 SIGNAL(relationshipsRemoved(QList<QContactLocalId>)));
1677     }
1678 }
1679
1680 void
1681 QContactTrackerEngine::disconnectSignals()
1682 {
1683     if (d->m_changeListener) {
1684         d->m_changeListener->disconnect(this);
1685         d->m_changeListener = 0;
1686     }
1687 }
1688
1689 void
1690 QContactTrackerEngine::registerGcQuery()
1691 {
1692     d->m_gcQueryId = QString::fromLatin1("com.nokia.qtcontacts-tracker.%1").arg(graphIri());
1693     QctGarbageCollector::registerQuery(d->m_gcQueryId, cleanupQueryString());
1694 }
1695
1696 void
1697 QContactTrackerEngine::connectNotify(const char *signal)
1698 {
1699     if (0 == d->m_changeListener) {
1700         // Create the change listener on demand:
1701         // Creating the listener is expensive as we must subscribe to some DBus signals.
1702         // Also watching DBus without any specific need wastes energy by wakeing up processes.
1703
1704         typedef QHash<QString, QctTrackerChangeListener *> ListenersByUri;
1705         static QThreadStorage<ListenersByUri *> listeners;
1706
1707         if (not listeners.hasLocalData()) {
1708             listeners.setLocalData(new ListenersByUri);
1709         }
1710
1711         const QString uri = managerUri();
1712         d->m_changeListener = listeners.localData()->value(uri);
1713
1714         if (0 == d->m_changeListener) {
1715             d->m_changeListener = new QctTrackerChangeListener(this, qApp);
1716             listeners.localData()->insert(uri, d->m_changeListener);
1717         }
1718
1719         connectSignals();
1720     }
1721
1722     QContactManagerEngine::connectNotify(signal);
1723 }
1724
1725 void
1726 QContactTrackerEngine::requestDestroyed(QContactAbstractRequest *request)
1727 {
1728     QCT_SYNCHRONIZED(&d->m_requestLifeGuard);
1729     QCT_SYNCHRONIZED_WRITE(&d->m_tableLock);
1730
1731     if (0 == request) {
1732         return;
1733     }
1734
1735     QTrackerAbstractRequest *const worker = d->m_workersByRequest.take(request);
1736
1737     if (0 != worker) {
1738         d->m_requestsByWorker.remove(worker);
1739     }
1740 }
1741
1742 void
1743 QContactTrackerEngine::onRequestDestroyed(QObject *req)
1744 {
1745     // dynamic_cast<> does not work at this point (has a 0 result, or even crashes) because the
1746     // derived parts of the class have already been destroyed by the time the base QObject's
1747     // destructor has emitted this signal. So we do a regular static case, because
1748     // requestDestroyed(QContactAbstractRequest*) just compares the pointer value anyway.
1749     requestDestroyed(static_cast<QContactAbstractRequest *>(req));
1750 }
1751
1752 bool
1753 QContactTrackerEngine::waitForRequestFinished(QContactAbstractRequest *request, int msecs)
1754 {
1755     if (not hasDebugFlag(SkipNagging)) {
1756         qctWarn(QString::fromLatin1("\n"
1757             "=============================================================================\n"
1758             "WARNING /!\\ - NEVER EVER CALL THIS FUNCTION FROM PRODUCTION CODE!!!\n"
1759             "=============================================================================\n"
1760             "QContactAbstractRequest::waitForFinished(), respectively\n"
1761             "QContactManagerEngine::waitForRequestFinished() must spin your\n"
1762             "application's event loop. Doing so will cause HAVOC AND PAIN for\n"
1763             "any non-trivial program.\n"
1764             "\n"
1765             "So please refactor your asynchronious code, or use the synchronious\n"
1766             "API if blocking your application is acceptable.\n"
1767             "\n"
1768             "WE MEAN IT!!!\n"
1769             "\n"
1770             "Offending application is %1 [%2].\n"
1771             "=============================================================================").
1772             arg(QCoreApplication::applicationFilePath(),
1773                 QString::number(QCoreApplication::applicationPid())));
1774     }
1775
1776     return waitForRequestFinishedImpl(request, msecs);
1777 }
1778
1779 bool
1780 QContactTrackerEngine::waitForRequestFinishedImpl(QContactAbstractRequest *request, int msecs)
1781 {
1782     if (0 == request) {
1783         return false;
1784     }
1785
1786     RequestEventLoop eventLoop(request, msecs);
1787
1788     if (not eventLoop.isFinished()) {
1789         eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
1790     }
1791
1792     return eventLoop.isFinished();
1793 }
1794
1795 bool
1796 QContactTrackerEngine::runSyncRequest(QContactAbstractRequest *request,
1797                                       QContactManager::Error *error) const
1798 {
1799     // Copy the engine to prevent forwarding side-effects to the caller's engine.
1800     // It costs a bit, but guess that's a justified penalty to all sync API users.
1801     QContactTrackerEngine taskEngine(*this);
1802
1803     QctTaskWaiter waiter(taskEngine.startRequestImpl(request));
1804
1805     if (not waiter.wait(requestTimeout())) {
1806         qctPropagate(QContactManager::UnspecifiedError, error);
1807         return false;
1808     }
1809
1810     qctPropagate(request->error(), error);
1811
1812     return true;
1813 }
1814
1815 ////////////////////////////////////////////////////////////////////////////////////////////////////
1816 // Synthesized contact details
1817 ////////////////////////////////////////////////////////////////////////////////////////////////////
1818
1819 QString
1820 QContactTrackerEngine::synthesizedDisplayLabel(const QContact& contact,
1821                                                QContactManager::Error* error) const
1822 {
1823     qctPropagate(QContactManager::NoError, error);
1824
1825     return createDisplayLabel(contact);
1826 }
1827
1828 QContact
1829 QContactTrackerEngine::compatibleContact(const QContact &original, QContactManager::Error* error) const
1830 {
1831     QContact contact = original;
1832
1833     foreach(const QTrackerContactDetail &detail, schema(contact.type()).details()) {
1834         QList<QContactDetail> contactDetails = contact.details(detail.name());
1835
1836         if (contactDetails.empty()) {
1837             continue;
1838         }
1839
1840         // Check that detail respects our schema's cardinality
1841         if (not detail.isUnique()) {
1842             continue;
1843         }
1844
1845         if (contactDetails.count() < 2) {
1846             continue;
1847         }
1848
1849         qctWarn(QString::fromLatin1("Dropping odd details: %2 detail must be unique").
1850                 arg(detail.name()));
1851
1852         for(int i = 1; i < contactDetails.count(); ++i) {
1853             contact.removeDetail(&contactDetails[i]);
1854         }
1855     }
1856
1857     // Check fields contents
1858     foreach(const QTrackerContactDetail &detail, schema(contact.type()).details()) {
1859         QList<QContactDetail> contactDetails = contact.details(detail.name());
1860
1861         foreach (QContactDetail contactDetail, contactDetails) {
1862             const QVariantMap detailValues = contactDetail.variantValues();
1863
1864             for (QVariantMap::ConstIterator it = detailValues.constBegin();
1865                  it != detailValues.constEnd(); ++it) {
1866                 const QTrackerContactDetailField *field = detail.field(it.key());
1867
1868                 if (field == 0) {
1869                     // We can't validate custom fields, skip
1870                     continue;
1871                 }
1872
1873                 QVariant computedValue;
1874
1875                 if (not field->makeValue(it.value(), computedValue)) {
1876                     // Apparently this is not an error, we just prune the field...
1877                     // makeValue already prints a debug message if it returns false,
1878                     // so we can stay silent here. No idea how UI can provide any
1879                     // useful feedback out of that.
1880                     contactDetail.removeValue(it.key());
1881                     break;
1882                 }
1883
1884                 // The computed value is only useful to generate SPARQL queries,
1885                 // we should not save it back to the field.
1886             }
1887
1888             contact.saveDetail(&contactDetail);
1889         }
1890     }
1891
1892     qctPropagate(QContactManager::NoError, error);
1893
1894     return contact;
1895 }
1896
1897 ////////////////////////////////////////////////////////////////////////////////////////////////////
1898 // Unsupported functions
1899 ////////////////////////////////////////////////////////////////////////////////////////////////////
1900
1901 bool
1902 QContactTrackerEngine::validateContact(const QContact&, QContactManager::Error* error) const
1903 {
1904     REPORT_UNSUPPORTED_FUNCTION(error);
1905     return false;
1906 }
1907
1908 bool
1909 QContactTrackerEngine::validateDefinition(const QContactDetailDefinition&,
1910                                           QContactManager::Error* error) const
1911 {
1912     REPORT_UNSUPPORTED_FUNCTION(error);
1913     return false;
1914 }
1915
1916 bool
1917 QContactTrackerEngine::saveDetailDefinition(const QContactDetailDefinition&, const QString&,
1918                                             QContactManager::Error* error)
1919 {
1920     REPORT_UNSUPPORTED_FUNCTION(error);
1921     return false;
1922 }
1923
1924 bool
1925 QContactTrackerEngine::removeDetailDefinition(const QString&, const QString&,
1926                                               QContactManager::Error* error)
1927 {
1928     REPORT_UNSUPPORTED_FUNCTION(error);
1929     return false;
1930 }
1931
1932 bool
1933 QContactTrackerEngine::cancelRequest(QContactAbstractRequest *request)
1934 {
1935     if (0 == request) {
1936         return false;
1937     }
1938
1939     QCT_SYNCHRONIZED(&d->m_requestLifeGuard);
1940     QCT_SYNCHRONIZED_READ(&d->m_tableLock);
1941
1942     QTrackerAbstractRequest *const worker = d->m_workersByRequest.value(request);
1943
1944     if (0 != worker) {
1945         return worker->cancel();
1946     }
1947     return false;
1948 }
1949
1950 bool
1951 QContactTrackerEngine::isRelationshipTypeSupported(const QString& relationshipType,
1952                                                    const QString& contactType) const
1953 {
1954     if (relationshipType == QContactRelationship::HasMember
1955             && supportedContactTypes().contains(contactType)
1956             && (contactType == QContactType::TypeContact
1957                 || contactType == QContactType::TypeGroup)) {
1958         return true;
1959     }
1960
1961     return false;
1962 }