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