Changes: Use QSparqlQueryOptions in runQuery
[qtcontacts-tracker:hasselmms-qtcontacts-tracker.git] / src / engine / contactsaverequest.cpp
1 /** This file is part of QtContacts tracker storage plugin
2  **
3  ** Copyright (c) 2010-2011 Nokia Corporation and/or its subsidiary(-ies).
4  **
5  ** Contact:  Nokia Corporation (info@qt.nokia.com)
6  **
7  ** GNU Lesser General Public License Usage
8  ** This file may be used under the terms of the GNU Lesser General Public License
9  ** version 2.1 as published by the Free Software Foundation and appearing in the
10  ** file LICENSE.LGPL included in the packaging of this file.  Please review the
11  ** following information to ensure the GNU Lesser General Public License version
12  ** 2.1 requirements will be met:
13  ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
14  **
15  ** In addition, as a special exception, Nokia gives you certain additional rights.
16  ** These rights are described in the Nokia Qt LGPL Exception version 1.1, included
17  ** in the file LGPL_EXCEPTION.txt in this package.
18  **
19  ** Other Usage
20  ** Alternatively, this file may be used in accordance with the terms and
21  ** conditions contained in a signed written agreement between you and Nokia.
22  **/
23
24 #include "contactsaverequest.h"
25 #include "engine.h"
26 #include "guidalgorithm.h"
27
28 #include <dao/contactdetail.h>
29 #include <dao/contactdetailschema.h>
30 #include <dao/sparqlconnectionmanager.h>
31 #include <dao/sparqlresolver.h>
32 #include <dao/subject.h>
33 #include <dao/support.h>
34
35 #include <lib/avatarutils.h>
36 #include <lib/customdetails.h>
37 #include <lib/requestextensions.h>
38 #include <lib/settings.h>
39
40 #include <QtSparql>
41
42 ///////////////////////////////////////////////////////////////////////////////////////////////////
43
44 CUBI_USE_NAMESPACE
45 CUBI_USE_NAMESPACE_RESOURCES
46
47 ///////////////////////////////////////////////////////////////////////////////////////////////////
48
49 typedef QMap<QString, Value> AffiliationMap;
50 typedef QList<class ExplictObjectInfo> ExplictObjectInfoList;
51 typedef QList<class ForeignObjectInfo> ForeignObjectInfoList;
52 typedef QMultiHash<PropertyInfoList, const QString> ResourceTypes;
53 typedef QList<class DetailMapping> DetailMappingList;
54 typedef QHash<PropertyInfoList, Value> PredicateValueHash;
55
56 ///////////////////////////////////////////////////////////////////////////////////////////////////
57
58 class DetailMappingData : public QSharedData
59 {
60     friend class DetailMapping;
61
62 public: // constructors
63     DetailMappingData(const QContactDetail &genericDetail,
64                       const QTrackerContactDetail *trackerDetail)
65         : m_genericDetail(genericDetail)
66         , m_trackerDetail(trackerDetail)
67         , m_contextsNormalized(false)
68     {
69     }
70
71 private: // fields
72     QContactDetail                  m_genericDetail;
73     const QTrackerContactDetail *   m_trackerDetail;
74     bool                            m_contextsNormalized : 1;
75 };
76
77 ///////////////////////////////////////////////////////////////////////////////////////////////////
78
79 class DetailMapping
80 {
81 public: // constructors
82     DetailMapping(const QContactDetail &genericDetail,
83                   const QTrackerContactDetail *trackerDetail)
84         : d(new DetailMappingData(genericDetail, trackerDetail))
85     {
86     }
87
88 public: // attributes
89     /// a detail which has no own ontology representation
90     bool isCustomDetail() const;
91
92     /// is a detail which is generated from other details and not stored itself
93     bool isSyntheticDetail() const;
94
95
96     QString definitionName() const;
97     QStringList contexts() const;
98     bool hasContext() const;
99
100     QString detailUri() const;
101     QStringList linkedDetailUris() const;
102
103     QVariant fieldValue(const QString &fieldName) const;
104     QVariantMap fieldValues() const;
105
106     const QList<QTrackerContactDetailField> & trackerFields() const;
107     const QTrackerContactDetailField * subTypeField() const;
108     QTrackerContactSubject::Scheme detailUriScheme() const;
109
110 public: // methods
111     QString makeResourceIri() const;
112     bool updateDetailUri(const DetailMappingList &mappings);
113
114 private: // fields
115     QExplicitlySharedDataPointer<DetailMappingData> d;
116 };
117
118 ///////////////////////////////////////////////////////////////////////////////////////////////////
119
120 class RelatedObjectInfoData : public QSharedData
121 {
122     friend class RelatedObjectInfo;
123
124 public:
125     virtual ~RelatedObjectInfoData() {}
126
127 protected: // constructor
128     RelatedObjectInfoData(const QString &typeIri, const QTrackerContactDetail &detail)
129         : m_typeIri(typeIri)
130         , m_hasCustomFields(not detail.customValueChains().isEmpty())
131     {
132         QSet<QString> predicateIriSet;
133         QSet<QString> subTypeIriSet;
134
135         foreach(const QTrackerContactDetailField &field, detail.fields()) {
136             if (field.permitsCustomValues()) {
137                 predicateIriSet += nao::hasProperty::iri();
138             }
139
140             if (field.hasSubTypes()) {
141                 foreach(const ClassInfoBase &ci, field.subTypeClasses()) {
142                     subTypeIriSet += ci.iri();
143                 }
144
145                 continue;
146             }
147
148             if (field.isWithoutMapping() || field.isReadOnly() || field.isInverse()) {
149                 continue;
150             }
151
152             predicateIriSet += field.propertyChain().last().iri();
153
154             foreach(const PropertyInfoBase &pi, field.computedProperties()) {
155                 predicateIriSet += pi.iri();
156             }
157         }
158
159         m_predicateIris = predicateIriSet.toList();
160         m_subTypeIris = subTypeIriSet.toList();
161     }
162
163 private: // fields
164     QString m_typeIri;
165     QStringList m_predicateIris;
166     QStringList m_subTypeIris;
167     bool m_hasCustomFields : 1;
168 };
169
170 class RelatedObjectInfo
171 {
172 protected: // constructor
173     RelatedObjectInfo(RelatedObjectInfoData *data)
174         : d(data)
175     {
176     }
177
178 public: // operators
179     RelatedObjectInfo(const RelatedObjectInfo &other)
180         : d(other.d)
181     {
182     }
183
184     RelatedObjectInfo & operator=(const RelatedObjectInfo &other)
185     {
186         return d = other.d, *this;
187     }
188
189 public: // attributes
190     const QString & typeIri() const { return d->m_typeIri; }
191     const QStringList & predicateIris() const { return d->m_predicateIris; }
192     const QStringList & subTypeIris() const { return d->m_subTypeIris; }
193     bool hasCustomFields() const { return d->m_hasCustomFields; }
194
195 protected: // fields
196     QExplicitlySharedDataPointer<RelatedObjectInfoData> d;
197 };
198
199 ///////////////////////////////////////////////////////////////////////////////////////////////////
200
201 class ExplictObjectInfoData : public RelatedObjectInfoData
202 {
203     friend class ExplictObjectInfo;
204
205 private: // constructor
206     ExplictObjectInfoData(const QString &typeIri, const QString &objectIri,
207                           const QTrackerContactDetail &detail)
208         : RelatedObjectInfoData(typeIri, detail)
209         , m_objectIri(objectIri)
210     {
211     }
212
213 private: // fields
214     QString m_objectIri;
215 };
216
217 class ExplictObjectInfo : public RelatedObjectInfo
218 {
219 public: // constructor
220     ExplictObjectInfo(const QString &typeIri, const QString &objectIri,
221                       const QTrackerContactDetail &detail)
222         : RelatedObjectInfo(new ExplictObjectInfoData(typeIri, objectIri, detail))
223     {
224     }
225
226 public: // attributes
227     const QString & objectIri() const { return data()->m_objectIri; }
228
229 protected:
230     const ExplictObjectInfoData * data() const
231     {
232         return static_cast<const ExplictObjectInfoData *>(d.data());
233     }
234 };
235
236 ///////////////////////////////////////////////////////////////////////////////////////////////////
237
238 class ForeignObjectInfoData : public RelatedObjectInfoData
239 {
240     friend class ForeignObjectInfo;
241
242 private: // constructor
243     ForeignObjectInfoData(const QString &typeIri, const QString &keyPropertyIri,
244                           const QVariant &keyValue, const QTrackerContactDetail &detail)
245         : RelatedObjectInfoData(typeIri, detail)
246         , m_keyPropertyIri(keyPropertyIri)
247         , m_keyValue(keyValue)
248     {
249     }
250
251 private: // fields
252     QString m_keyPropertyIri;
253     QVariant m_keyValue;
254 };
255
256 class ForeignObjectInfo : public RelatedObjectInfo
257 {
258 public: // constructor
259     ForeignObjectInfo(const QString &typeIri, const QString &keyPropertyIri,
260                       const QVariant &keyValue, const QTrackerContactDetail &detail)
261         : RelatedObjectInfo(new ForeignObjectInfoData(typeIri, keyPropertyIri, keyValue, detail))
262     {
263     }
264
265 public: // attributes
266     const QString & keyPropertyIri() const { return data()->m_keyPropertyIri; }
267     const QVariant & keyValue() const { return data()->m_keyValue; }
268
269 protected:
270     const ForeignObjectInfoData * data() const
271     {
272         return static_cast<const ForeignObjectInfoData *>(d.data());
273     }
274 };
275
276 ///////////////////////////////////////////////////////////////////////////////////////////////////
277
278 class UpdateBuilder
279 {
280 public: // constructors
281     UpdateBuilder(const QTrackerContactSaveRequest *request,
282                   const QContact &contact, const QString &contactIri);
283
284 public: // attributes
285     const QContactGuid & guidDetail() const { return m_guidDetail; }
286     bool hadGuid() const { return m_hadGuid; }
287
288     const QDateTime & created() const { return m_created; }
289     bool hadCreated() const { return m_hadCreated; }
290
291     const QDateTime & accessed() const { return m_accessed; }
292     const QDateTime & lastModified() const { return m_lastModified; }
293
294     const DetailMappingList & detailMappings() const { return m_detailMappings; }
295     const QTrackerContactDetailSchema & schema() const { return m_schema; }
296
297     QString queryString();
298
299 protected: // attributes
300     const QctLogger & qctLogger() const { return m_logger; }
301
302 protected: // methods
303     void collectInsertions();
304
305     QString makeUniqueName(const QString &basename = QLatin1String("_"));
306
307     Value & lookupAffiliation(const QString &context);
308
309     void restrictAffiliation(const Value &contact,
310                              const Value &affiliation,
311                              const QString &context);
312
313     void insert(const Value &subject, const Value &predicate,
314                 const Value &value, bool explicitValue = true,
315                 int position = -1);
316
317     void insertCustomValues(const Value &subject, const QString &detailName,
318                             const QString &fieldName, const QVariant &value,
319                             const QVariantList &allowableValues = QVariantList());
320
321     void insertCustomValues(const Value &subject, const QString &detailName,
322                             const QTrackerContactDetailField &field, const QVariant &value);
323
324     void insertCustomDetail(Value &subject, const DetailMapping &detail);
325
326     void insertDetailField(const Value &subject, const DetailMapping &detail,
327                            const QTrackerContactDetailField &field, const QVariant &value,
328                            const ResourceTypes &types, PredicateValueHash &objectCache);
329
330     void appendPredicateChain(const QString &prefix,
331                               const PropertyInfoList::ConstIterator &begin,
332                               const PropertyInfoList::ConstIterator &end,
333                               const QString &target, const QString &suffix);
334
335     void collectRelatedObjects();
336     void deleteRelatedObjects();
337     void deleteContactProperties();
338
339     void insertForeignKeyObjects();
340
341 private: // fields
342     const QctLogger &m_logger;
343     const QTrackerContactDetailSchema &m_schema;
344     ResourceValue m_graph;
345
346     const QContactLocalId m_contactLocalId;
347     const QString m_contactIri;
348     QContactGuid m_guidDetail;
349     QDateTime m_accessed;
350     QDateTime m_created;
351     QDateTime m_lastModified;
352     DetailMappingList m_detailMappings;
353
354     AffiliationMap m_affiliations;
355
356     bool m_hadGuid : 1;
357     bool m_hadCreated : 1;
358
359     QList<QTrackerContactDetail> m_implictlyRelatedObjects;
360     QMap<QString, ExplictObjectInfoList> m_explicitlyRelatedObjects;
361     QMap<QString, ForeignObjectInfoList> m_foreignKeyRelatedObjects;
362     QList<Pattern> m_explicitInsertStatements;
363     PatternGroup m_explicitInsertRestrictions;
364     QList<Insert> m_implicitInsertStatements;
365     QString m_queryString;
366     int m_variableCounter;
367
368     Options::SparqlOptions m_sparqlOptions;
369 };
370
371 ///////////////////////////////////////////////////////////////////////////////////////////////////
372
373
374 template <typename T>
375 inline bool operator<(const QList<T> &a, const QList<T> &b)
376 {
377     for(int i = 0; i < a.count() && i < b.count(); ++i) {
378         if (a[i] < b[i]) {
379             return true;
380         } else if (b[i] < a[i]) {
381             return false;
382         }
383     }
384
385     return a.count() < b.count();
386 }
387
388 ///////////////////////////////////////////////////////////////////////////////////////////////////
389
390 inline bool
391 DetailMapping::isCustomDetail() const
392 {
393     return (0 == d->m_trackerDetail);
394 }
395
396 inline bool
397 DetailMapping::isSyntheticDetail() const
398 {
399     return (0 != d->m_trackerDetail && d->m_trackerDetail->isSynthesized());
400 }
401
402 QStringList
403 DetailMapping::contexts() const
404 {
405     if (0 != d->m_trackerDetail && not d->m_trackerDetail->hasContext()) {
406         return QStringList();
407     }
408
409     if (not d->m_contextsNormalized) {
410         QStringList normalizedContexts;
411
412         foreach(const QString &context, d->m_genericDetail.contexts().toSet()) {
413             normalizedContexts += qctCamelCase(context);
414         }
415
416         d->m_genericDetail.setContexts(normalizedContexts);
417         d->m_contextsNormalized = true;
418     }
419
420     return d->m_genericDetail.contexts();
421 }
422
423 bool
424 DetailMapping::hasContext() const
425 {
426     return (0 != d->m_trackerDetail && d->m_trackerDetail->hasContext());
427 }
428
429 inline QString
430 DetailMapping::definitionName() const
431 {
432     return d->m_genericDetail.definitionName();
433 }
434
435 inline QString
436 DetailMapping::detailUri() const
437 {
438     return d->m_genericDetail.detailUri();
439 }
440
441 inline QStringList
442 DetailMapping::linkedDetailUris() const
443 {
444     return d->m_genericDetail.linkedDetailUris();
445 }
446
447 inline QVariant
448 DetailMapping::fieldValue(const QString &fieldName) const
449 {
450     return d->m_genericDetail.variantValue(fieldName);
451 }
452
453 inline QVariantMap
454 DetailMapping::fieldValues() const
455 {
456     return d->m_genericDetail.variantValues();
457 }
458
459 inline const QList<QTrackerContactDetailField> &
460 DetailMapping::trackerFields() const
461 {
462     if (0 == d->m_trackerDetail) {
463         static const QList<QTrackerContactDetailField> nothing;
464         return nothing;
465     }
466
467     return d->m_trackerDetail->fields();
468 }
469
470 inline const QTrackerContactDetailField *
471 DetailMapping::subTypeField() const
472 {
473     return (0 != d->m_trackerDetail ? d->m_trackerDetail->subTypeField() : 0);
474 }
475
476 inline QTrackerContactSubject::Scheme
477 DetailMapping::detailUriScheme() const
478 {
479     return (0 != d->m_trackerDetail ? d->m_trackerDetail->detailUriScheme()
480                                     : QTrackerContactSubject::None);
481 }
482
483 QString
484 DetailMapping::makeResourceIri() const
485 {
486     QTrackerContactSubject::Scheme scheme = QTrackerContactSubject::None;
487     QVariantList values;
488
489     // build new detail URI from current detail content
490     foreach(const QTrackerContactDetailField &field, trackerFields()) {
491         PropertyInfoList::ConstIterator detailUriProperty = field.detailUriProperty();
492
493         if (detailUriProperty == field.propertyChain().constEnd()) {
494             continue;
495         }
496
497         // don't mess with read only fields
498         if (field.propertyChain().first().isReadOnly()) {
499             continue;
500         }
501
502         // get schema from very first detail URI field
503         if (values.isEmpty()) {
504             scheme = detailUriProperty->resourceIriScheme();
505
506             if (QTrackerContactSubject::None == scheme) {
507                 qctWarn(QString::fromLatin1("detail URI property of %1 detail's %2 field "
508                                             "doesn't have an IRI scheme assigned").
509                         arg(definitionName(), field.name()));
510                 continue;
511             }
512         }
513
514         // collect value of this detail URI field
515         values.append(fieldValue(field.name()));
516     }
517
518     // detail URI doesn't use field values, abort
519     if (values.isEmpty()) {
520         return QString();
521     }
522
523     return QTrackerContactSubject::makeIri(scheme, values);
524 }
525
526 bool
527 DetailMapping::updateDetailUri(const DetailMappingList &mappings)
528 {
529     // skip custom details
530     if (isCustomDetail()) {
531         return false;
532     }
533
534     const QString oldDetailUri = detailUri();
535     const QString newDetailUri = makeResourceIri();
536
537     // abort if nothing to see
538     if (newDetailUri.isEmpty() || newDetailUri == oldDetailUri) {
539         return false;
540     }
541
542     // update detail URI
543     d->m_genericDetail.setDetailUri(newDetailUri);
544
545     // update details pointing to this one
546     foreach(const DetailMapping &otherDetail, mappings) {
547         QStringList linkedDetails = otherDetail.linkedDetailUris();
548
549         // try to remove old URI from list of linked details
550         if (linkedDetails.removeOne(oldDetailUri)) {
551             // detail was linked, therefore update the link
552             otherDetail.d->m_genericDetail.setLinkedDetailUris(linkedDetails << newDetailUri);
553         }
554     }
555
556     return true;
557 }
558
559 ////////////////////////////////////////////////////////////////////////////////////////////////////
560
561 QTrackerContactSaveRequest::QTrackerContactSaveRequest(QContactAbstractRequest *request,
562                                                        QContactTrackerEngine *engine,
563                                                        QObject *parent)
564     : QTrackerBaseRequest<QContactSaveRequest>(engine, parent)
565     , m_contacts(staticCast(request)->contacts())
566     , m_nameOrder(QctRequestExtensions::get(request)->nameOrder())
567     , m_timestamp(QDateTime::currentDateTime())
568     , m_batchSize(0)
569 {
570 }
571
572 QTrackerContactSaveRequest::~QTrackerContactSaveRequest()
573 {
574 }
575
576 ///////////////////////////////////////////////////////////////////////////////////////////////////
577
578 static ResourceTypes
579 collectResourceTypes(const DetailMapping &detail)
580 {
581     ResourceTypes types;
582
583     foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
584         if (field.propertyChain().isEmpty()) {
585             continue;
586         }
587
588         PropertyInfoList predicates;
589
590         const PropertyInfoList::ConstIterator end(field.propertyChain().constEnd() - 1);
591         for(PropertyInfoList::ConstIterator pi(field.propertyChain().constBegin()); pi != end; ++pi) {
592             const PropertyInfoBase &npi(*(pi + 1)); // next predicate
593
594             predicates += *pi;
595
596             if (npi.isInverse()) {
597                 types.insertMulti(predicates, npi.rangeIri());
598             } else {
599                 types.insertMulti(predicates, npi.domainIri());
600             }
601
602             if (pi->isInverse()) {
603                 types.insertMulti(predicates, pi->domainIri());
604             } else {
605                 types.insertMulti(predicates, pi->rangeIri());
606             }
607         }
608
609         if (field.hasSubTypeClasses()) {
610             QVariant subTypeValue(detail.fieldValue(field.name()));
611             QSet<QString> subTypes;
612
613             if (subTypeValue.isNull() && not field.defaultValue().isNull()) {
614                 subTypeValue = field.defaultValue();
615             }
616
617             switch(subTypeValue.type()) {
618             case QVariant::String:
619                 subTypes.insert(subTypeValue.toString());
620                 break;
621
622             case QVariant::StringList:
623                 subTypes.unite(subTypeValue.toStringList().toSet());
624                 break;
625
626             default:
627                 qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
628                         arg(QLatin1String(subTypeValue.typeName()),
629                             detail.definitionName(), field.name()));
630                 continue;
631             }
632
633             predicates.append(field.propertyChain().last());
634
635             foreach(const ClassInfoBase &i, field.subTypeClasses()) {
636                 if (subTypes.contains(i.text())) {
637                     types.insertMulti(predicates, i.iri());
638                 }
639             }
640         }
641     }
642
643     return types;
644 }
645
646 static QVariant
647 findSubTypeValue(const DetailMapping &detail, const QTrackerContactDetailField *const subTypeField)
648 {
649     QVariant value;
650
651     if (0 != subTypeField && subTypeField->hasSubTypeProperties()) {
652         value = detail.fieldValue(subTypeField->name());
653
654         if (QVariant::StringList == value.type()) {
655             // courteously pick first list element if the user ignores our detail schema
656             QStringList valueList = value.toStringList();
657
658             if (not valueList.isEmpty()) {
659                 value = valueList.first();
660             } else {
661                 value = subTypeField->defaultValue();
662             }
663
664             if (valueList.size() > 1) {
665                 qctWarn(QString::fromLatin1("Subtype field %3 of %2 detail is of type %1, "
666                                             "picking first value from supplied list.").
667                         arg(QLatin1String(QVariant::typeToName(subTypeField->dataType())),
668                             detail.definitionName(), subTypeField->name()));
669             }
670         }
671
672         // barf if the subtype value is not useful at all
673         if (not value.isNull() && QVariant::String != value.type()) {
674             qctWarn(QString::fromLatin1("Invalid value type %1 for subtype field %3 of %2 detail").
675                     arg(QLatin1String(value.typeName()), detail.definitionName(),
676                         subTypeField->name()));
677             value = QVariant();
678         }
679     }
680
681     return value;
682
683 }
684
685 static const ResourceValue
686 findSubTypePredicate(const DetailMapping &detail, const QTrackerContactDetailField &field,
687                      const QTrackerContactDetailField *const subTypeField)
688 {
689     const QVariant subTypeValue = findSubTypeValue(detail, subTypeField);
690
691     if (subTypeValue.isValid()) {
692         foreach(const PropertyInfoBase &pi, subTypeField->subTypeProperties()) {
693             if (pi.value() == subTypeValue) {
694                 return pi.resource();
695             }
696         }
697     }
698
699     return field.propertyChain().last().resource();
700 }
701
702 QString
703 UpdateBuilder::makeUniqueName(const QString &basename)
704 {
705     return QString::fromLatin1("%1%2").arg(basename, QString::number(m_variableCounter++));
706 }
707
708 static QString
709 name(const QString &prefix, const QString &suffix)
710 {
711     if (not suffix.isEmpty()) {
712         return prefix + QLatin1Char('_') + suffix;
713     }
714
715     return prefix;
716 }
717
718 Value &
719 UpdateBuilder::lookupAffiliation(const QString &context)
720 {
721     AffiliationMap::Iterator affiliation = m_affiliations.find(context);
722
723     // create nco::Affiliation resource when needed
724     if (affiliation == m_affiliations.end()) {
725         const QString baseName = name(QLatin1String("Affiliation"), context);
726         affiliation = m_affiliations.insert(context, BlankValue(makeUniqueName(baseName)));
727    }
728
729     return *affiliation;
730 }
731
732 void
733 UpdateBuilder::insertCustomValues(const Value &subject,
734                                   const QString &detailName, const QString &fieldName,
735                                   const QVariant &value, const QVariantList &allowableValues)
736 {
737     QVariantList customValues;
738
739     switch(value.type()) {
740     case QVariant::StringList:
741         foreach(const QString &element, value.toStringList()) {
742             if (not element.isEmpty() && not allowableValues.contains(element)) {
743                 customValues.append(element);
744             }
745         }
746
747         break;
748
749     case QVariant::List:
750         foreach(const QVariant &element, value.toList()) {
751             if (not element.isNull() && not allowableValues.contains(element)) {
752                 customValues.append(element);
753             }
754         }
755
756         break;
757
758     default:
759         if (not value.isNull() && not allowableValues.contains(value)) {
760             customValues.append(value);
761         }
762     }
763
764     foreach(const QVariant &element, customValues) {
765         Value property = BlankValue(makeUniqueName(name(detailName, fieldName)));
766
767         insert(subject, rdf::type::resource(), nie::InformationElement::resource());
768         insert(subject, nao::hasProperty::resource(), property);
769
770         insert(property, rdf::type::resource(), nao::Property::resource());
771         insert(property, nao::propertyName::resource(), LiteralValue(fieldName));
772         insert(property, nao::propertyValue::resource(), LiteralValue(element));
773     }
774 }
775
776 void
777 UpdateBuilder::insertCustomValues(const Value &subject, const QString &detailName,
778                                   const QTrackerContactDetailField &field,
779                                   const QVariant &value)
780 {
781     insertCustomValues(subject, detailName, field.name(), value,
782                        field.describeAllowableValues());
783 }
784
785 static PropertyInfoList::ConstIterator
786 endOfPropertyChain(const QTrackerContactDetailField &field)
787 {
788     if (field.isWithoutMapping() || not field.hasLiteralValue()) {
789         return field.propertyChain().constEnd();
790     }
791
792     return field.propertyChain().constEnd() - 1;
793 }
794
795 void
796 UpdateBuilder::insertDetailField(const Value &subject, const DetailMapping &detail,
797                                  const QTrackerContactDetailField &field, const QVariant &value,
798                                  const ResourceTypes &types, PredicateValueHash &objectCache)
799 {
800     if (not value.isValid()) {
801         qctWarn(QString::fromLatin1("Value must be valid for %2 field of %1 detail").
802                 arg(detail.definitionName(), field.name()));
803         return;
804     }
805
806     const QTrackerContactDetailField *const subTypeField = detail.subTypeField();
807     const PropertyInfoList &propertyChain = field.propertyChain();
808
809     if (propertyChain.isEmpty()) {
810         qctWarn(QString::fromLatin1("RDF property chain needed for %2 field of %1 detail").
811                 arg(detail.definitionName(), field.name()));
812         return;
813     }
814
815     if (propertyChain.first().isReadOnly()) {
816         return;
817     }
818
819     PropertyInfoList predicateChain;
820     QList<Value> axis;
821     axis.append(subject);
822
823     const PropertyInfoList::ConstIterator end = endOfPropertyChain(field);
824     const PropertyInfoList::ConstIterator tail = propertyChain.constEnd() - 1;
825
826     const Value literalValue = qctMakeCubiValue(value);
827     bool literalValuePending = true;
828
829     for(PropertyInfoList::ConstIterator pi = propertyChain.constBegin(); pi != end; ++pi) {
830         if (pi->isReadOnly()) {
831             break;
832         }
833
834         PredicateValueHash::ConstIterator object = objectCache.find(predicateChain << *pi);
835
836         if (object == objectCache.constEnd()) {
837             // figure out subject IRI for this field
838             QString subjectIri;
839
840             const PropertyInfoList::ConstIterator npi = pi + 1;
841
842             if (npi != propertyChain.constEnd()) {
843                 const QTrackerContactSubject::Scheme resourceIriScheme = npi->resourceIriScheme();
844
845                 if (QTrackerContactSubject::isContentScheme(resourceIriScheme)) {
846                     // Prefer the detail's explicit URI for tail nodes, but only if the
847                     // property's subject scheme matches the detail's scheme. This additional
848                     // check is needed because details like Organization randomly spread their
849                     // fields over many different resource types.
850                     if (npi == tail && resourceIriScheme == detail.detailUriScheme()) {
851                         subjectIri = detail.detailUri();
852                     }
853
854                     // If we don't have an resource IRI yet generate it from property and value.
855                     if (subjectIri.isEmpty()) {
856                         subjectIri = npi->makeResourceIri(value);
857                     }
858                 }
859             }
860
861             // name object variable and assign subject IRI
862             Value objectVariable;
863
864             if (not subjectIri.isEmpty()) {
865                 // assign generated subject IRI
866                 objectVariable = ResourceValue(subjectIri);
867             } else if (npi != propertyChain.constEnd() || field.isWithoutMapping()) {
868                 // create named blank variable if no subject IRI could be built. If the
869                 // field has no mapping, then it's saved using a nao:Property, so create
870                 // a blank node for that resource.
871                 const QString basename = name(detail.definitionName(), QLatin1String("Resource"));
872
873                 // if we're on the end of the property chanin this objectVariable
874                 // is about to become axis.last()
875                 // Now we have to check if it's a foreign value and make it a normal
876                 // variable, not a named blank variable if that's the case
877                 if ((not field.isWithoutMapping()) &&
878                     field.propertyChain().last().isForeignKey() &&
879                     (npi + 1 == propertyChain.constEnd())) {
880                     objectVariable = Variable(makeUniqueName(basename));
881                 } else {
882                     objectVariable = BlankValue(makeUniqueName(basename));
883                 }
884             } else {
885                 objectVariable = literalValue;
886                 literalValuePending = false;
887             }
888
889             // assign RDF classes to this field
890             ResourceTypes::ConstIterator fieldResource = types.find(predicateChain);
891
892             if (types.constEnd() == fieldResource) {
893                 qctWarn(QString::fromLatin1("Cannot find resource types for %3 property "
894                                             "of %1 detail's %2 field").
895                         arg(detail.definitionName(), field.name(),
896                             predicateChain.last().iri()));
897             }
898
899             for(; fieldResource != types.constEnd() && fieldResource.key() == predicateChain; ++fieldResource) {
900                 // create insert statement of this resource type
901                 object = objectCache.insert(predicateChain, objectVariable);
902                 insert(*object, rdf::type::resource(), ResourceValue(fieldResource.value()));
903             }
904         }
905
906         // create insert statement with the field's value
907         if (pi->isInverse()) {
908             insert(*object, pi->resource(), axis.last());
909         } else {
910             insert(axis.last(), pi->resource(), *object);
911         }
912
913         axis.append(*object);
914     }
915
916     // insert custom values for subTypes field
917     if (subTypeField && subTypeField->permitsCustomValues()) {
918         insertCustomValues(axis.last(), detail.definitionName(), *subTypeField,
919                            detail.fieldValue(subTypeField->name()));
920     }
921
922     // find proper type of the value to insert
923     if (not field.isWithoutMapping()) {
924         const ResourceValue subTypePredicate = findSubTypePredicate(detail, field, subTypeField);
925         const PropertyInfoBase & valueProperty = field.propertyChain().last();
926
927         // store field value
928         if (literalValuePending) {
929             if (valueProperty.isInverse()) {
930                 insert(literalValue, subTypePredicate, axis.last());
931             } else {
932                 insert(axis.last(), subTypePredicate, literalValue);
933             }
934         }
935
936         // restrict object variable if current property describes a foreign key
937         if (valueProperty.isForeignKey()) {
938             switch(valueProperty.caseSensitivity()) {
939             case Qt::CaseSensitive:
940                 m_explicitInsertRestrictions.addPattern(axis.last(), valueProperty.resource(), literalValue);
941                 break;
942
943             case Qt::CaseInsensitive:
944                 Variable var;
945                 PatternGroup caseInsensitiveFilter;
946                 caseInsensitiveFilter.addPattern(axis.last(), valueProperty.resource(), var);
947                 caseInsensitiveFilter.setFilter(Functions::equal.apply(Functions::lowerCase.apply(var),
948                                                                        Functions::lowerCase.apply(literalValue)));
949                 m_explicitInsertRestrictions.addPattern(caseInsensitiveFilter);
950                 break;
951             }
952         }
953     }
954
955     // insert computed values
956     foreach(const PropertyInfoBase &pi, field.computedProperties()) {
957         QVariant computedValue;
958
959         if (pi.conversion()->makeValue(value, computedValue)) {
960             insert(axis.last(), pi.resource(), qctMakeCubiValue(computedValue));
961         }
962     }
963
964     // store custom values
965     if (field.permitsCustomValues()) {
966         insertCustomValues(axis.last(), detail.definitionName(), field, value);
967     }
968 }
969
970 UpdateBuilder::UpdateBuilder(const QTrackerContactSaveRequest *request,
971                              const QContact &contact, const QString &contactIri)
972     : m_logger(request->qctLogger())
973     , m_schema(request->engine()->schema(contact.type()))
974     , m_graph(request->engine()->graphIri())
975     , m_contactLocalId(contact.localId())
976     , m_contactIri(contactIri)
977     , m_variableCounter(1)
978     , m_sparqlOptions(Options::DefaultSparqlOptions)
979 {
980     if (request->engine()->hasDebugFlag(QContactTrackerEngine::ShowUpdates)) {
981         m_sparqlOptions |= Options::PrettyPrint;
982     }
983
984     // figure out the contact's timestamps
985    const QContactTimestamp timestampDetail = contact.detail<QContactTimestamp>();
986
987    m_accessed = timestampDetail.variantValue(QContactTimestamp__FieldAccessedTimestamp).toDateTime();
988    m_created = timestampDetail.created();
989    m_lastModified = timestampDetail.lastModified();
990
991    if (m_accessed.isNull()) {
992        m_accessed = request->timestamp();
993    }
994
995    if (m_created.isNull()) {
996        m_created = request->timestamp();
997        m_hadCreated = (0 == m_contactLocalId); // no need to delete timestamp for new contacts
998    } else {
999        m_hadCreated = true;
1000    }
1001
1002    if (m_lastModified.isNull() || 0 != m_contactLocalId) {
1003        // only preserve the lastModified field for new contacts...
1004        m_lastModified = request->timestamp();
1005    }
1006
1007    // figure out the contact's GUID
1008    m_guidDetail = contact.detail<QContactGuid>();
1009    m_hadGuid = not m_guidDetail.guid().isEmpty();
1010
1011    if (not m_hadGuid) {
1012        m_guidDetail = request->engine()->guidAlgorithm().makeGuid(contact);
1013        m_hadGuid = (0 == m_contactLocalId); // no need to delete GUID for new contacts
1014    }
1015
1016    // collect named details
1017    QMap<QString, QContactDetail> namedDetails;
1018
1019    foreach(const QContactDetail &detail, contact.details()) {
1020        if (not detail.detailUri().isEmpty()) {
1021            namedDetails.insert(detail.detailUri(), detail);
1022        }
1023    }
1024
1025    // collect details and update their URIs
1026    foreach(const QContactDetail &detail, contact.details()) {
1027        if (detail.accessConstraints() & QContactDetail::ReadOnly) {
1028            continue; // don't mess with read-only details, e.g. those merged from social providers
1029        }
1030
1031        const QTrackerContactDetail *definition(m_schema.detail(detail.definitionName()));
1032
1033        if (0 == definition) {
1034            if (m_schema.isSyntheticDetail(detail.definitionName())) {
1035                continue; // don't store synthesized details as nao:Property
1036            }
1037        } else {
1038            // Details like are QContactAvatar are synthetized, and must be turned into
1039            // internal details like QContactPersonalAvatar or QContactOnlineAvatar.
1040            definition = definition->findImplementation(m_schema, namedDetails, detail);
1041        }
1042
1043        m_detailMappings.append(DetailMapping(detail, definition));
1044    }
1045
1046    foreach(DetailMapping detail, m_detailMappings) {
1047        detail.updateDetailUri(m_detailMappings);
1048    }
1049 }
1050
1051 void
1052 UpdateBuilder::insert(const Value &subject, const Value &predicate,
1053                       const Value &value, bool explicitValue,
1054                       int position)
1055 {
1056     if (explicitValue) {
1057         if (position >= 0) {
1058             m_explicitInsertStatements.insert(position, Pattern(subject, predicate, value));
1059         } else {
1060             m_explicitInsertStatements.append(Pattern(subject, predicate, value));
1061         }
1062     } else {
1063         PatternGroup optional;
1064         PatternGroup filter;
1065         Variable var;
1066
1067         Insert insert = Insert(m_graph);
1068         insert.addData(subject, predicate, value);
1069         optional.addPattern(subject, predicate, var);
1070         filter.setFilter(Functions::not_.apply(Functions::bound.apply(var)));
1071         optional.setOptional(true);
1072         insert.addRestriction(optional);
1073         insert.addRestriction(filter);
1074         m_implicitInsertStatements += insert;
1075     }
1076 }
1077
1078 void
1079 UpdateBuilder::insertCustomDetail(Value &subject, const DetailMapping &detail)
1080 {
1081     const QString detailName = detail.definitionName();
1082     const QVariantMap fields = detail.fieldValues();
1083
1084     Value detailProperty = BlankValue(makeUniqueName(detailName));
1085
1086     insert(subject, nao::hasProperty::resource(), detailProperty);
1087     insert(detailProperty, rdf::type::resource(), nao::Property::resource());
1088     insert(detailProperty, nao::propertyName::resource(), LiteralValue(detailName));
1089
1090     for(QVariantMap::ConstIterator i = fields.constBegin(); i != fields.constEnd(); ++i) {
1091         insertCustomValues(detailProperty, detailName, i.key(), i.value());
1092     }
1093 }
1094
1095 void
1096 UpdateBuilder::restrictAffiliation(const Value &contact,
1097                                    const Value &affiliation,
1098                                    const QString &context)
1099 {
1100     insert(affiliation, rdf::type::resource(), nco::Affiliation::resource());
1101     if (not context.isEmpty()) {
1102         insert(affiliation, rdfs::label::resource(), LiteralValue(context));
1103     }
1104     insert(contact, nco::hasAffiliation::resource(), affiliation);
1105 }
1106
1107 void
1108 UpdateBuilder::collectInsertions()
1109 {
1110     m_explicitInsertStatements.clear();
1111     // Removing Role properties also removed the contact's PersonContact type,
1112     // so we have to restore it.
1113     ResourceValue contact = ResourceValue(m_contactIri);
1114
1115     // Restore the resource type, we removed it for quick propertly deletion.
1116     foreach(const QString &classIri, schema().contactClassIris()) {
1117         insert(contact, rdf::type::resource(), ResourceValue(classIri));
1118     }
1119
1120     // Prepare affiliation for work context in case the contact has an organization detail.
1121     // This one should be stored with the same affiliation as the work context.
1122     Value affiliation = lookupAffiliation(QContactDetail::ContextWork);
1123
1124     // Collect inserts for each regular detail.
1125     foreach(const DetailMapping &detail, detailMappings()) {
1126         // Store custom details via nao:Proprty.
1127         if (detail.isCustomDetail()) {
1128             insertCustomDetail(contact, detail);
1129             continue;
1130         }
1131
1132         // Don't save synthesized details.
1133         if (detail.isSyntheticDetail()) {
1134             continue;
1135         }
1136
1137         const QVariantMap detailValues = detail.fieldValues();
1138         const ResourceTypes resourceTypes = collectResourceTypes(detail);
1139         PredicateValueHash objectCache;
1140
1141         foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
1142             if (field.hasSubTypes()) {
1143                 // Subtypes are stored indirectly by assigning the proper resource class
1144                 // to the RDF resource representing this field, or by selecting the matching
1145                 // RDF property for storing the detail value.
1146                 continue;
1147             }
1148
1149             const QVariantMap::ConstIterator fieldValue = detailValues.find(field.name());
1150
1151             if (fieldValue == detailValues.constEnd()) {
1152                 // Could not find value for this field.
1153                 continue;
1154             }
1155
1156             QVariant rdfValue;
1157
1158             if (not field.makeValue(*fieldValue, rdfValue)) {
1159                 // Could not compute the RDF value for this field.
1160                 continue;
1161             }
1162
1163             if (not detail.hasContext()) {
1164                 insertDetailField(contact, detail, field, rdfValue, resourceTypes, objectCache);
1165             } else {
1166                 QSet<QString> detailContexts = detail.contexts().toSet();
1167
1168                 if (detailContexts.isEmpty()) {
1169                     detailContexts.insert(QString());
1170                 }
1171
1172                 foreach(const QString &context, detailContexts) {
1173                     Value contextAffiliation = lookupAffiliation(context);
1174                     restrictAffiliation(contact, contextAffiliation, context);
1175
1176                     insertDetailField(contextAffiliation, detail, field,
1177                                       rdfValue, resourceTypes, objectCache);
1178                 }
1179             }
1180         }
1181     }
1182
1183     // Check if the prepared affiliation was used for binding contact details.
1184     // In that case this prepare affiliation must be turned into a proper one.
1185     // we need to insert _:affiliation nco:org _:Organization before
1186     // <contact:42> nco:hasAffiliation _:Organization, so we look for the first
1187     // use of _:affiliation as an object
1188     for (int i = 0; i < m_explicitInsertStatements.size(); ++i) {
1189         const Pattern &statement = m_explicitInsertStatements.at(i);
1190
1191         if (statement.object().sparql() == affiliation.sparql()) {
1192             // FIXME: Revert to restrictAffiliation() once Cubi supports gropuing
1193             //        of insert statements
1194             insert(affiliation, rdfs::label::resource(), LiteralValue(QContactDetail::ContextWork), true, i);
1195             insert(affiliation, rdf::type::resource(), nco::Affiliation::resource(), true, i);
1196             break;
1197         }
1198     }
1199
1200     // Insert timestamps and such.
1201     insert(contact, nie::contentLastModified::resource(), LiteralValue(lastModified()));
1202     insert(contact, nie::contentCreated::resource(), LiteralValue(created()), hadCreated());
1203     insert(contact, nco::contactUID::resource(), LiteralValue(guidDetail().guid()), hadGuid());
1204
1205     // Add prepared INSERT statements to the query string.
1206     Insert insert = Insert();
1207     Graph graph = Graph(m_graph);
1208     foreach (const Pattern& data, m_explicitInsertStatements) {
1209         graph.addPattern(data);
1210     }
1211     insert.addData(graph);
1212     insert.addRestriction(m_explicitInsertRestrictions);
1213
1214     m_queryString += insert.sparql(m_sparqlOptions);
1215
1216     foreach(const Insert &statement, m_implicitInsertStatements) {
1217         m_queryString += statement.sparql(m_sparqlOptions);
1218     }
1219 }
1220
1221 void
1222 UpdateBuilder::collectRelatedObjects()
1223 {
1224     m_implictlyRelatedObjects.clear();
1225     m_explicitlyRelatedObjects.clear();
1226     m_foreignKeyRelatedObjects.clear();
1227
1228     foreach(const QTrackerContactDetail &detail, schema().details()) {
1229         const QTrackerContactDetailField *const subjectField = detail.resourceIriField();
1230
1231         if (0 != subjectField) {
1232             const PropertyInfoList &subjectPropertyChain = subjectField->propertyChain();
1233             const PropertyInfoBase &subjectProperty = *subjectField->detailUriProperty();
1234
1235             if (subjectPropertyChain.first().isReadOnly()) {
1236                 continue;
1237             }
1238
1239             if (QTrackerContactSubject::isContentScheme(subjectProperty.resourceIriScheme())) {
1240                 // delete related objects by their content IRI
1241                 foreach(const DetailMapping &mapping, detailMappings()) {
1242                     if (mapping.definitionName() != detail.name()) {
1243                         continue;
1244                     }
1245
1246                     const QString &typeIri = subjectProperty.resourceTypeIri();
1247                     const QString &objectIri = mapping.makeResourceIri();
1248
1249                     if (objectIri.isEmpty()) {
1250                         qctWarn(QString::fromLatin1("Empty object IRI for %1 detail which "
1251                                                     "has a content IRI").arg(detail.name()));
1252                         continue;
1253                     }
1254
1255                     m_explicitlyRelatedObjects[detail.name()] +=
1256                             ExplictObjectInfo(typeIri, objectIri, detail);
1257                 }
1258
1259                 continue; // next detail
1260             }
1261
1262             if (subjectField->isForeignKey()) {
1263                 foreach(const DetailMapping &mapping, detailMappings()) {
1264                     const QString &typeIri = subjectProperty.resourceTypeIri();
1265                     const QString &keyIri = subjectField->propertyChain().last().iri();
1266                     const QVariant &value = mapping.fieldValue(subjectField->name());
1267
1268                     if (value.isNull()) {
1269                         continue; // no such field
1270                     }
1271
1272                     m_foreignKeyRelatedObjects[detail.name()] +=
1273                             ForeignObjectInfo(typeIri, keyIri, value, detail);
1274                 }
1275
1276                 continue; // next detail
1277             }
1278         }
1279
1280         // no content IRI known, delete via association
1281         if (not detail.predicateChains().isEmpty()) {
1282             m_implictlyRelatedObjects += detail;
1283         }
1284     }
1285 }
1286
1287 void
1288 UpdateBuilder::appendPredicateChain(const QString &prefix,
1289                                     const PropertyInfoList::ConstIterator &begin,
1290                                     const PropertyInfoList::ConstIterator &end,
1291                                     const QString &target, const QString &suffix)
1292 {
1293     if (begin != end) {
1294         PropertyInfoList::ConstIterator predicate = begin;
1295
1296         m_queryString += prefix;
1297         m_queryString += QLatin1Char('<');
1298         m_queryString += predicate->iri();
1299         m_queryString += QLatin1String("> ");
1300
1301         while(++predicate != end) {
1302             m_queryString += QLatin1String("[ <");
1303             m_queryString += predicate->iri();
1304             m_queryString += QLatin1String("> ");
1305         }
1306
1307         m_queryString += target;
1308
1309         for(int i = (end - begin); i > 1; --i) {
1310             m_queryString += QLatin1String(" ]");
1311         }
1312
1313         m_queryString += suffix;
1314     }
1315 }
1316
1317 void
1318 UpdateBuilder::deleteRelatedObjects()
1319 {
1320     // SPARQL fragments used for building the query.
1321     static const QString implicitQueryPrefix = QLatin1String
1322             ("\n"
1323              "DELETE\n"
1324              "{\n"
1325              "  GRAPH <%2>\n"
1326              "  {\n"
1327              "    ?subject <%1> ?object .\n"
1328              "  }\n"
1329              "}\n"
1330              "WHERE\n"
1331              "{\n"
1332              "  GRAPH <%2>\n"
1333              "  {\n");
1334     static const QString implicitQuerySuffix = QLatin1String
1335             ("    ?subject <%1> ?object .\n"
1336              "  }\n"
1337              "}\n");
1338
1339     static const QString implicitResourcePrefix = QLatin1String("  <%1> ");
1340     static const QString implicitResourceSuffix = QLatin1String(" .\n");
1341
1342     static const QString implicitQueryPredicateQuery = QLatin1String
1343             ("\n"
1344              "DELETE\n"
1345              "{\n"
1346              "  GRAPH <%2>\n"
1347              "  {\n"
1348              "    ?resource a rdfs:Resource .\n"
1349              "  }\n"
1350              "}\n"
1351              "WHERE\n"
1352              "{\n"
1353              "  GRAPH <%2>\n"
1354              "  {\n"
1355              "    <%1> ?predicate ?resource .\n"
1356              "  }\n"
1357              "  FILTER(?predicate IN (<%3>)) .\n"
1358              "}\n");
1359
1360     static const QString explictObjectPredicateQuery = QLatin1String
1361             ("\n"
1362              "DELETE\n"
1363              "{\n"
1364              "  <%1> ?predicate ?object .\n"
1365              "}\n"
1366              "WHERE\n"
1367              "{\n"
1368              "  <%1> ?predicate ?object .\n"
1369              "  FILTER(?predicate IN (<%2>)) .\n"
1370              "}\n");
1371     static const QString explictObjectSubTypeQuery = QLatin1String
1372             ("\n"
1373              "DELETE\n"
1374              "{\n"
1375              "  ?resource a <%1> .\n"
1376              "}\n"
1377              "WHERE\n"
1378              "{\n"
1379              "  ?resource a <%2> .\n"
1380              "  FILTER(?resource IN (<%3>)) .\n"
1381              "}\n");
1382     static const QString foreignObjectQuery = QLatin1String
1383             ("\n"
1384              "DELETE\n"
1385              "{\n"
1386              "  ?resource nao:hasProperty ?property .\n"
1387              "}\n"
1388              "WHERE\n"
1389              "{\n"
1390              "  ?resource nao:hasProperty ?property ; <%3> ?key .\n"
1391              "  FILTER(?key IN (%4)) .\n"
1392              "}\n"
1393              "\n"
1394              "DELETE\n"
1395              "{\n"
1396              "  ?resource a <%1> .\n"
1397              "}\n"
1398              "WHERE\n"
1399              "{\n"
1400              "  ?resource a <%2> ; <%3> ?key .\n"
1401              "  FILTER(?key IN (%4)) .\n"
1402              "}\n");
1403
1404     // Glue together the SPARQL fragments.
1405
1406 #ifndef QT_NO_DEBUG
1407     m_queryString += QLatin1String
1408             ("\n"
1409              "#----------------------------------------------------.\n"
1410              "# Delete associated objects via their property chain |\n"
1411              "#----------------------------------------------------'\n");
1412 #endif // QT_NO_DEBUG
1413
1414
1415     if (0 != m_contactLocalId) {
1416         QStringList shortChains(nao::hasProperty::iri());
1417
1418         const QString graphIri = m_graph.iri();
1419         foreach(const QTrackerContactDetail &detail, m_implictlyRelatedObjects) {
1420             foreach(const PropertyInfoList &chain, detail.possessedChains()) {
1421                 const QString predicateIri = chain.last().iri();
1422
1423                 if (detail.hasContext()) {
1424                     PropertyInfoList contextChain;
1425                     contextChain += piHasAffiliation;
1426                     contextChain += chain;
1427
1428                     m_queryString += implicitQueryPrefix.arg(predicateIri, graphIri);
1429                     appendPredicateChain(implicitResourcePrefix.arg(m_contactIri),
1430                                          contextChain.constBegin(), contextChain.constEnd() - 1,
1431                                          QLatin1String("?subject"),
1432                                          implicitResourceSuffix);
1433                     m_queryString += implicitQuerySuffix.arg(predicateIri);
1434                 }
1435
1436                 if (chain.length() > 1) {
1437                     m_queryString += implicitQueryPrefix.arg(predicateIri, graphIri);
1438                     appendPredicateChain(implicitResourcePrefix.arg(m_contactIri),
1439                                          chain.constBegin(), chain.constEnd() - 1,
1440                                          QLatin1String("?subject"),
1441                                          implicitResourceSuffix);
1442                     m_queryString += implicitQuerySuffix.arg(predicateIri);
1443                 } else {
1444                     shortChains += chain.first().iri();
1445                 }
1446             }
1447         };
1448
1449         const QString shortChainIris = shortChains.join(QLatin1String(">, <"));
1450         m_queryString += implicitQueryPredicateQuery.arg(m_contactIri, graphIri, shortChainIris);
1451     }
1452
1453 #ifndef QT_NO_DEBUG
1454     m_queryString += QLatin1String
1455             ("\n"
1456              "#------------------------------------------------------------.\n"
1457              "# Delete associated objects via their well known content IRI |\n"
1458              "#------------------------------------------------------------'\n");
1459 #endif // QT_NO_DEBUG
1460
1461     foreach(const ExplictObjectInfoList &objects, m_explicitlyRelatedObjects) {
1462         QStringList objectIriList;
1463
1464         foreach(const ExplictObjectInfo &info, objects) {
1465             if (not info.predicateIris().isEmpty()) {
1466                 const QString predicateIris = info.predicateIris().join(QLatin1String(">, <"));
1467                 m_queryString += explictObjectPredicateQuery.arg(info.objectIri(), predicateIris);
1468             }
1469
1470             if (not info.subTypeIris().isEmpty()) {
1471                 objectIriList += info.objectIri();
1472             }
1473         }
1474
1475         if (not objects.first().subTypeIris().isEmpty()) {
1476             const QString &subTypeIris = objects.first().subTypeIris().join(QLatin1String(">, <"));
1477             const QString &objectIris = objectIriList.join(QLatin1String(">, <"));
1478             const QString &typeIri = objects.first().typeIri();
1479
1480             m_queryString += explictObjectSubTypeQuery.arg(subTypeIris, typeIri, objectIris);
1481         }
1482     }
1483
1484 #ifndef QT_NO_DEBUG
1485     m_queryString += QLatin1String
1486             ("\n"
1487              "#-------------------------------------------------------.\n"
1488              "# Delete associated objects via their foreign key value |\n"
1489              "#-------------------------------------------------------'\n");
1490 #endif // QT_NO_DEBUG
1491
1492     foreach(const ForeignObjectInfoList &objects, m_foreignKeyRelatedObjects) {
1493         QStringList foreignKeyList;
1494         QString keyPropertyIri;
1495         QString subTypeIris;
1496
1497         foreach(const ForeignObjectInfo &info, objects) {
1498             if (not info.subTypeIris().isEmpty()) {
1499                 if (foreignKeyList.isEmpty()) {
1500                     subTypeIris = info.subTypeIris().join(QLatin1String(">, <"));
1501                     keyPropertyIri = info.keyPropertyIri();
1502                 }
1503
1504                 foreignKeyList += qctMakeCubiValue(info.keyValue()).sparql();
1505             }
1506         }
1507
1508         if (not foreignKeyList.isEmpty()) {
1509             const QString &keyLiterals = foreignKeyList.join(QLatin1String(", "));
1510             const QString &typeIri = objects.first().typeIri();
1511
1512             // FIXME: consider case sensitivity
1513             m_queryString += foreignObjectQuery.arg(subTypeIris, typeIri,
1514                                                     keyPropertyIri, keyLiterals);
1515         }
1516     }
1517 }
1518
1519 void
1520 UpdateBuilder::deleteContactProperties()
1521 {
1522     // SPARQL fragments used for building the query.
1523     static const QString queryPrefix = QLatin1String
1524             ("\n"
1525 #ifndef QT_NO_DEBUG
1526              "#---------------------------------------------------------.\n"
1527              "# Delete the contact properties within the plugin's graph |\n"
1528              "#---------------------------------------------------------'\n"
1529              "\n"
1530 #endif // QT_NO_DEBUG
1531              "DELETE\n"
1532              "{\n"
1533              "  GRAPH <%2>\n"
1534              "  {\n"
1535              "    <%1> ?predicate ?object .\n"
1536              "  }\n"
1537              "}\n"
1538              "WHERE\n"
1539              "{\n"
1540              "  GRAPH <%2>\n"
1541              "  {\n"
1542              "    <%1> ?predicate ?object .\n"
1543              "    FILTER(?predicate NOT IN(rdf:type,nco:belongsToGroup");
1544     static const QString guidFilter = QLatin1String(",nco:contactUID");
1545     static const QString createdFilter = QLatin1String(",nie:contentCreated");
1546     static const QString querySuffix = QLatin1String(")) .\n"
1547              "  }\n"
1548              "}\n");
1549
1550     if (0 != m_contactLocalId) {
1551         // Glue together the SPARQL fragments.
1552         m_queryString += queryPrefix.arg(m_contactIri, m_graph.iri());
1553
1554         if (not hadGuid()) {
1555             // Preserve possibly existing GUID if the caller didn't provide one.
1556             m_queryString += guidFilter;
1557         }
1558
1559         if (not hadCreated()) {
1560             // Preserve possibly existing creation timestamp  if the caller didn't provide one.
1561             m_queryString += createdFilter;
1562         }
1563
1564         m_queryString += querySuffix;
1565     }
1566 }
1567
1568 QString
1569 UpdateBuilder::queryString()
1570 {
1571     m_queryString.clear();
1572
1573     // collect DELETE statements
1574     collectRelatedObjects();
1575     deleteRelatedObjects();
1576     deleteContactProperties();
1577
1578     // collect INSERT statements
1579     insertForeignKeyObjects();
1580     collectInsertions();
1581
1582     return m_queryString;
1583 }
1584
1585 void
1586 QTrackerContactSaveRequest::normalizeContact(QContact &contact) const
1587 {
1588     contact = engine()->compatibleContact(contact, 0);
1589
1590     engine()->updateDisplayLabel(contact, m_nameOrder);
1591
1592     // Add "addressbook" sync target if no sync target set
1593     // For contacts that have their sync target set to "telepathy", we need to
1594     // reset the sync target to "addressbook". This is done so that the contact
1595     // will not get deleted by contactsd if the IM account is removed, and the
1596     // user will not loose any data he/she might have added to the contact.
1597     // QContactSyncTarget is a unique detail in our schema so at this point we
1598     // are sure there is at most one detail set (since normalizeContact() was
1599     // already called).
1600     QContactSyncTarget syncTarget = contact.detail<QContactSyncTarget>();
1601
1602     if (syncTarget.isEmpty() || engine()->isWeakSyncTarget(syncTarget.syncTarget())) {
1603         syncTarget.setSyncTarget(engine()->syncTarget());
1604         contact.saveDetail(&syncTarget);
1605     }
1606 }
1607
1608 static QSet<QString>
1609 findAccountDetailUris(const QContact &contact)
1610 {
1611     QSet<QString> onlineAccountUris;
1612
1613     foreach(const QContactOnlineAccount &detail, contact.details<QContactOnlineAccount>()) {
1614         onlineAccountUris += detail.detailUri();
1615     }
1616
1617     return onlineAccountUris;
1618 }
1619
1620 static bool
1621 isLinkedDetail(const QContactDetail &detail, const QSet<QString> &linkedDetailUris)
1622 {
1623     foreach(const QString &uri, detail.linkedDetailUris()) {
1624         if (linkedDetailUris.contains(uri)) {
1625             return true;
1626         }
1627     }
1628
1629     return false;
1630 }
1631
1632 QContactManager::Error
1633 QTrackerContactSaveRequest::writebackThumbnails(QContact &contact) const
1634 {
1635     const QSet<QString> accountDetailUris = findAccountDetailUris(contact);
1636
1637     // Find user supplied thumbnail of the contact itself.
1638     const QList<QContactThumbnail> thumbnailDetails = contact.details<QContactThumbnail>();
1639     QContactThumbnail defaultThumbnail;
1640
1641     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1642         qDebug()
1643                 << metaObject()->className() << m_stopWatch.elapsed()
1644                 << ": found" << thumbnailDetails.count() << "thumbnail detail(s)";
1645     }
1646
1647     foreach(const QContactThumbnail &thumbnail, thumbnailDetails) {
1648         if (not isLinkedDetail(thumbnail, accountDetailUris)) {
1649             defaultThumbnail = thumbnail;
1650             break;
1651         }
1652     }
1653
1654     if (defaultThumbnail.isEmpty()) {
1655         return QContactManager::NoError;
1656     }
1657
1658     // Find default avatar of the contact itself.
1659     QContactAvatar defaultAvatar;
1660
1661     foreach(const QContactAvatar &avatar, contact.details<QContactAvatar>()) {
1662         if (not isLinkedDetail(avatar, accountDetailUris)) {
1663             defaultAvatar = avatar;
1664             break;
1665         }
1666     }
1667
1668     // Get avatar thumbnail from detail and scale down when needed.
1669     QFile::FileError fileError = QFile::NoError;
1670     QImage thumbnailImage = defaultThumbnail.thumbnail();
1671     const QString avatarCacheFileName = qctWriteThumbnail(thumbnailImage, &fileError);
1672
1673     if (avatarCacheFileName.isEmpty()) {
1674         // failed to save avatar picture - remove avatar detail
1675         contact.removeDetail(&defaultAvatar);
1676
1677         return (QFile::ResizeError == fileError ? QContactManager::OutOfMemoryError
1678                                                 : QContactManager::UnspecifiedError);
1679     }
1680
1681     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1682         qDebug() << metaObject()->className() << m_stopWatch.elapsed()
1683                  << ": writing default thumbnail:" << avatarCacheFileName;
1684     }
1685
1686     // everything went fine - update avatar detail and proceed
1687     defaultAvatar.setImageUrl(QUrl::fromLocalFile(avatarCacheFileName));
1688     contact.saveDetail(&defaultAvatar);
1689
1690     return QContactManager::NoError;
1691 }
1692
1693 void
1694 UpdateBuilder::insertForeignKeyObjects()
1695 {
1696     // SPARQL fragments used for building the query.
1697     static const QString queryPrefix = QLatin1String
1698            ("\n"
1699             "INSERT\n"
1700             "{\n"
1701             "  _:_ a <%1> ; <%2> %3 .\n"
1702             "}\n"
1703             "WHERE\n"
1704             "{\n"
1705             "  OPTIONAL\n"
1706             "  {\n");
1707     static const QString caseSensitiveFilter = QLatin1String
1708            ("    ?resource a <%1> ; <%2> %3 .\n");
1709     static const QString caseInsensitiveFilter = QLatin1String
1710            ("    ?resource a <%1> ; <%2> ?value .\n"
1711             "    FILTER(fn:lower-case(%3) = fn:lower-case(?value)) .\n");
1712     static const QString querySuffix = QLatin1String
1713            ("  }\n"
1714             "\n"
1715             "  FILTER(!bound(?resource)) .\n"
1716             "}\n");
1717
1718     // Glue together the SPARQL fragments.
1719 #ifndef QT_NO_DEBUG
1720     m_queryString += QLatin1String
1721             ("\n"
1722              "#--------------------------------------------------.\n"
1723              "# Create shared objects referenced by foreign keys |\n"
1724              "#--------------------------------------------------'\n");
1725 #endif // QT_NO_DEBUG
1726
1727     foreach(const DetailMapping &detail, detailMappings()) {
1728         foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
1729             const PropertyInfoList::ConstIterator pi = field.foreignKeyProperty();
1730
1731             if (field.propertyChain().constEnd() == pi) {
1732                 continue; // skip field if no foreign key property found
1733             }
1734
1735             QVariant fieldValue = detail.fieldValue(field.name());
1736
1737             if (fieldValue.isNull()) {
1738                 continue; // skip field if no matching value stored in actual detail
1739             }
1740
1741             if (not field.makeValue(fieldValue, fieldValue)) {
1742                 continue; // skip field if value cannot be converted to storage type
1743             }
1744
1745
1746             QString domainIri = pi->domainIri();
1747             PropertyInfoBase parent = pi->parent();
1748
1749             if (parent && pi->domainIri() != parent.rangeIri()) {
1750                 domainIri += QLatin1String(">, <") + parent.rangeIri();
1751             }
1752
1753             const QString predicateIri = pi->iri();
1754             const QString literalValue = qctMakeCubiValue(fieldValue).sparql();
1755
1756             if (literalValue.isEmpty()) {
1757                 qctWarn(QString::fromLatin1("Empty literal value for %2 field of %1 detail: %3.").
1758                         arg(detail.definitionName(), field.name(), fieldValue.toString()));
1759                 continue;
1760             }
1761
1762             m_queryString += queryPrefix.arg(domainIri, predicateIri, literalValue);
1763
1764             switch(pi->caseSensitivity()) {
1765             case Qt::CaseSensitive:
1766                 m_queryString += caseSensitiveFilter.arg(domainIri, predicateIri, literalValue);
1767                 break;
1768             case Qt::CaseInsensitive:
1769                 m_queryString += caseInsensitiveFilter.arg(domainIri, predicateIri, literalValue);
1770                 break;
1771             }
1772
1773             m_queryString += querySuffix;
1774         }
1775     }
1776 }
1777
1778 QString
1779 QTrackerContactSaveRequest::queryString() const
1780 {
1781     QString queryString;
1782
1783     foreach(QContact contact, m_contacts) {
1784         const QString iri = makeAnonymousIri(QUuid::createUuid());
1785         queryString += UpdateBuilder(this, contact, iri).queryString();
1786     }
1787
1788     return queryString + cleanupQueryString();
1789 }
1790
1791 QString
1792 QTrackerContactSaveRequest::cleanupQueryString() const
1793 {
1794     // Notice FILTER in WHERE clause - garbage collector needs to verify parenting outside this graph too.
1795     // reason - IMAccount hasIMAddress in contactsd private graph so no need to delete it. The same could apply
1796     // to any other property - it could have been added to parent in some other graph.
1797     // Camera geotagging creates addresses outside qct graph and have no "parent" contact pointing
1798     // to them through nco:hasAddress
1799     static const QString obsoleteResourcesPrefix = QLatin1String
1800             ("\n"
1801              "DELETE\n"
1802              "{\n"
1803              "  GRAPH <%1>\n"
1804              "  {\n"
1805              "    ?resource a <%2> .\n"
1806              "  }\n"
1807              "}\n"
1808              "WHERE\n"
1809              "{\n"
1810              "  GRAPH <%1>\n"
1811              "  {\n"
1812              "    ?resource a <%3> .\n"
1813              "  }\n");
1814     static const QString imAccountStopPattern = QLatin1String
1815             ("    FILTER(NOT EXISTS { ?resource a nco:IMAccount }) .\n");
1816     static const QString obsoleteResourcesPattern = QLatin1String
1817             ("    FILTER(NOT EXISTS { ?parent <%1> ?resource }) .\n");
1818     static const QString obsoleteResourcesSuffix = QLatin1String
1819             ("}\n");
1820
1821     QString queryString;
1822
1823 #ifndef QT_NO_DEBUG
1824     queryString += QLatin1String
1825             ("\n"
1826              "#--------------------------------------.\n"
1827              "# Drop drop obsolete custom properties |\n"
1828              "#--------------------------------------'\n");
1829 #endif // QT_NO_DEBUG
1830
1831     // collect obsolete resource types and their predicates
1832     typedef QMap<QString, bool> PredicateMap;
1833     typedef QMap<QString, PredicateMap> ResourceTypePredicateMap;
1834     ResourceTypePredicateMap obsoleteResources;
1835
1836     foreach(const QTrackerContactDetailSchema &schema, engine()->schemas()) {
1837         foreach(const QTrackerContactDetail &detail, schema.details()) {
1838             foreach(const PropertyInfoList &chain, detail.possessedChains()) {
1839                 const PropertyInfoBase &pi = chain.last();
1840
1841                 if (pi.rangeIri() == rdfs::Resource::iri()) {
1842                     qctWarn(QString::fromLatin1("Not cleaning up obsolete resources for %1 property"
1843                                                 "since the property's range is too generic (%2).").
1844                             arg(qctIriAlias(pi.iri()), qctIriAlias(pi.rangeIri())));
1845                     continue;
1846                 }
1847
1848                 obsoleteResources[pi.rangeIri()].insert(pi.iri(), true);
1849             }
1850         }
1851     }
1852
1853     // glue everything together
1854     queryString += obsoleteResourcesPrefix.arg(engine()->graphIri(),
1855                                                nao::Property::iri(),
1856                                                nao::Property::iri());
1857     queryString += obsoleteResourcesPattern.arg(nao::hasProperty::iri());
1858     queryString += obsoleteResourcesSuffix;
1859
1860     for(ResourceTypePredicateMap::ConstIterator
1861         t = obsoleteResources.constBegin(); t != obsoleteResources.constEnd(); ++t) {
1862         queryString += obsoleteResourcesPrefix.arg(engine()->graphIri(),
1863                                                    rdfs::Resource::iri(),
1864                                                    t.key());
1865
1866         if (nco::IMAddress::iri() == t.key()) {
1867             // FIXME: Remove this workaround for NB#206404 - Saving a contact using
1868             // qtcontacts-tracker causes nco:IMAccount to be removed.
1869             queryString += imAccountStopPattern;
1870         }
1871
1872         foreach(const QString &predicate, t.value().keys()) {
1873             queryString += obsoleteResourcesPattern.arg(predicate);
1874         }
1875
1876         queryString += obsoleteResourcesSuffix;
1877     }
1878
1879     return queryString;
1880 }
1881
1882 ///////////////////////////////////////////////////////////////////////////////////////////////////
1883
1884 void
1885 QTrackerContactSaveRequest::saveContacts()
1886 {
1887     // figure out resource ids for the passed contacts
1888     if (not resolveContactIris()) {
1889         return;
1890     }
1891
1892     // update tracker with the contacts
1893     QSparqlConnection &connection = QctSparqlConnectionManager::defaultConnection();
1894
1895     if (not connection.isValid()) {
1896         reportError(QLatin1String("Cannot save contacts: No valid QtSparql connection."));
1897         return;
1898     }
1899
1900     for(int i = 0; i < m_contacts.count(); ++i) {
1901         QContact &contact = m_contacts[i];
1902
1903         if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1904             qDebug()
1905                     << metaObject()->className() << m_stopWatch.elapsed()
1906                     << ": contact" << i << "- updating";
1907         }
1908
1909         // drop odd details, add missing details
1910         normalizeContact(contact);
1911
1912         // normalize and save avatar
1913
1914         const QContactManager::Error thumbnailError = writebackThumbnails(contact);
1915         if (thumbnailError != QContactManager::NoError) {
1916             qctWarn(QString::fromLatin1("Cannot save avatar thumbnail for contact %1/%2").
1917                     arg(QString::number(i + 1), QString::number(m_contacts.count())));
1918             m_errorMap.insert(i, thumbnailError);
1919             continue;
1920         }
1921
1922         // build the update query
1923         UpdateBuilder builder(this, contact, m_contactIris.at(i));
1924         const QString queryString = builder.queryString();
1925
1926         // run the update query
1927         const QSparqlQuery query(queryString, QSparqlQuery::InsertStatement);
1928         QScopedPointer<QSparqlResult> result(runQuery(query, SyncQueryOptions, connection));
1929
1930         if (result.isNull()) {
1931             qctWarn(QString::fromLatin1("Save request failed for contact %1/%2").
1932                 arg(QString::number(i + 1), QString::number(m_contacts.count())));
1933             m_errorMap.insert(i, lastError());
1934             continue;
1935         }
1936
1937         if (result->hasError()) {
1938             qctWarn(QString::fromLatin1("Save request failed for contact %1/%2: %3").
1939                 arg(QString::number(i + 1), QString::number(m_contacts.count()),
1940                     qctTruncate(result->lastError().message())));
1941             m_errorMap.insert(i, translateError(result->lastError()));
1942             continue;
1943         }
1944     }
1945
1946     // update contact ids
1947     if (not resolveContactIds()) {
1948         return;
1949     }
1950
1951     // report result
1952     if (lastError() == QContactManager::NoError && not m_errorMap.isEmpty()) {
1953         setLastError(m_errorMap.constBegin().value());
1954     }
1955
1956     emitResultLater();
1957
1958     // FIXME: turn into proper QctTask
1959     // cleanup a bit when neccessary with __async__ query to avoid delaying the result.
1960     if (m_contacts.count() > 1) {
1961         const QSparqlQuery query(cleanupQueryString(), QSparqlQuery::DeleteStatement);
1962         QScopedPointer<QSparqlResult> result(runQuery(query, AsyncQueryOptions, connection, 0));
1963
1964         if (not result.isNull()) {
1965             result.take()->setParent(this);
1966         }
1967     }
1968 }
1969
1970 bool
1971 QTrackerContactSaveRequest::resolveContactIris()
1972 {
1973     // Collect contacts ids to resolve into IRIs
1974     QList<uint> idsToLookup;
1975
1976     foreach(const QContact &contact, m_contacts) {
1977         const QContactId contactId = contact.id();
1978
1979         if (contactId.managerUri() == engine()->managerUri() || contactId.managerUri().isEmpty()) {
1980             idsToLookup += contactId.localId();
1981         } else {
1982             idsToLookup += 0;
1983         }
1984     }
1985
1986     // Resolve contact IRIs
1987     QctResourceIriResolver resolver(idsToLookup);
1988
1989     if (not resolver.lookupAndWait()) {
1990         reportError(resolver.errors(), QLatin1String("Cannot resolve local ids of saved contacts"));
1991         return false;
1992     }
1993
1994     m_contactIris = resolver.resourceIris();
1995
1996     // Check if number of resolved tracker ids matches expectation.
1997     if (m_contacts.count() != m_contactIris.count()) {
1998         reportError(QLatin1String("Resolver provides invalid number of tracker ids"));
1999         return false;
2000     }
2001
2002     return true;
2003 }
2004
2005 bool
2006 QTrackerContactSaveRequest::resolveContactIds()
2007 {
2008     // We left the saveContact() loop, and don't have any pending updates or
2009     // pending commits. Let's figure out the local ids of the contacts.
2010     QctTrackerIdResolver resolver(m_contactIris);
2011
2012     if (not resolver.lookupAndWait()) {
2013         reportError(resolver.errors(), QLatin1String("Cannot resolve local ids of saved contacts"));
2014         return false;
2015     }
2016
2017     // Assign resolved ids to the individual contacts
2018     const QList<QContactLocalId> &resolvedIds = resolver.trackerIds();
2019
2020     for(int i = 0; i < resolvedIds.count(); ++i) {
2021         if (0 != resolvedIds[i]) {
2022             QContactId id = m_contacts.at(i).id();
2023             id.setManagerUri(engine()->managerUri());
2024             id.setLocalId(resolvedIds[i]);
2025             m_contacts[i].setId(id);
2026         } else if (not m_errorMap.contains(i)) {
2027             qctWarn(QString::fromLatin1("Cannot resolve local id for contact %1/%2 (%3)").
2028                     arg(QString::number(i + 1), QString::number(m_contacts.count()),
2029                         resolver.resourceIris().at(i)));
2030             m_errorMap.insert(i, QContactManager::UnspecifiedError);
2031         }
2032     }
2033
2034     return true;
2035 }
2036
2037 ///////////////////////////////////////////////////////////////////////////////////////////////////
2038
2039 void
2040 QTrackerContactSaveRequest::run()
2041 {
2042     setCancelable(false);
2043
2044     if (canceled()) {
2045         emitResultLater();
2046         return;
2047     }
2048
2049     if (m_contacts.isEmpty()) {
2050         emitResultLater();
2051         return;
2052     }
2053
2054     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
2055         qDebug()
2056                 << metaObject()->className() << 0
2057                 << ": number of contacts:" << m_contacts.count();
2058     }
2059
2060     m_stopWatch.start();
2061
2062     // FIXME: saveContacts() will block the thread's event queue for quite some time.
2063     // Therefore this callWhenReady() mechanism shall be replaced by some some proper QctTask.
2064     // Well, or we just drop custom GUID algorithms.
2065     engine()->guidAlgorithm().callWhenReady(this, SLOT(saveContacts()));
2066 }
2067
2068
2069 void
2070 QTrackerContactSaveRequest::emitResult(QContactManager::Error error)
2071 {
2072     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowTiming)) {
2073         qDebug()
2074                 << metaObject()->className()  << m_stopWatch.elapsed()
2075                 << ": reporting result" << error;
2076     }
2077
2078     engine()->updateContactSaveRequest(staticCast(engine()->request(this).data()),
2079                                        m_contacts, error, m_errorMap,
2080                                        QContactAbstractRequest::FinishedState);
2081 }
2082
2083 ///////////////////////////////////////////////////////////////////////////////////////////////////