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