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