Changes: Move more fields into UpdateBuilder
[qtcontacts-tracker:hasselmms-qtcontacts-tracker.git] / src / engine / contactsaverequest.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt Mobility Components.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "contactsaverequest.h"
43 #include "engine.h"
44
45 #include <dao/classhierarchy.h>
46 #include <dao/contactdetail.h>
47 #include <dao/contactdetailschema.h>
48 #include <dao/customdetails.h>
49 #include <dao/querybuilder.h>
50 #include <dao/settings.h>
51 #include <dao/subject.h>
52 #include <dao/support.h>
53
54 #include <dbus/tracker.h>
55
56 #include <QtGui/QImageWriter>
57
58 #include <QtTracker/ontologies/nao.h>
59 #include <QtTracker/ontologies/nie.h>
60
61 #include <errno.h>
62
63 ///////////////////////////////////////////////////////////////////////////////////////////////////
64
65 using namespace SopranoLive;
66 using namespace SopranoLive::Ontologies;
67
68 ///////////////////////////////////////////////////////////////////////////////////////////////////
69
70 typedef QList<class DetailMapping> DetailMappingList;
71 typedef QMultiHash<PredicateChain, const QUrl *> EntityHash;
72
73 ///////////////////////////////////////////////////////////////////////////////////////////////////
74
75 class DetailMappingData : public QSharedData
76 {
77     friend class DetailMapping;
78
79 public: // constructors
80     DetailMappingData(const QContactDetail &genericDetail,
81                       const QTrackerContactDetail *trackerDetail)
82         : m_genericDetail(genericDetail)
83         , m_trackerDetail(trackerDetail)
84     {
85     }
86
87 private: // fields
88     QContactDetail                  m_genericDetail;
89     const QTrackerContactDetail *   m_trackerDetail;
90 };
91
92 ///////////////////////////////////////////////////////////////////////////////////////////////////
93
94 class DetailMapping
95 {
96 public: // constructors
97     DetailMapping(const QContactDetail &genericDetail,
98                   const QTrackerContactDetail *trackerDetail)
99         : d(new DetailMappingData(genericDetail, trackerDetail))
100     {
101     }
102
103 public: // attributes
104     /// a detail which has no own ontology representation
105     bool isCustomDetail() const;
106
107     /// is a detail which is generated from other details and not stored itself
108     bool isSyntheticDetail() const;
109
110     /// is a detail which belongs to an affiliation, see "Work" context
111     bool isAffiliatedDetail() const;
112
113     /// is a detail which is directly belongs to the person, see "Home" context
114     bool isPersonalDetail() const;
115
116     QString definitionName() const;
117
118     QString detailUri() const;
119     QStringList linkedDetailUris() const;
120
121     QVariant fieldValue(const QString &fieldName) const;
122     QVariantMap fieldValues() const;
123
124     const QList<QTrackerContactDetailField> & trackerFields() const;
125     const QTrackerContactDetailField * subTypeField() const;
126     QTrackerContactSubject::Scheme detailScheme() const;
127
128 public: // methods
129     bool updateDetailUri(QContactLocalId contactId, const DetailMappingList &mappings);
130
131 private: // fields
132     QExplicitlySharedDataPointer<DetailMappingData> d;
133 };
134
135 ///////////////////////////////////////////////////////////////////////////////////////////////////
136
137 class UpdateBuilder
138 {
139 public:
140     UpdateBuilder(const QTrackerContactSaveRequest *request,
141                   const QContact &contact, const QDateTime &timestamp);
142
143     const QString & guid() const { return m_guid; }
144     bool hadGuid() const { return m_hadGuid; }
145
146     const QDateTime & accessed() const { return m_accessed; }
147     bool hadAccessed() const { return m_hadAccessed; }
148
149     const QDateTime & created() const { return m_created; }
150     bool hadCreated() const { return m_hadCreated; }
151
152     const QDateTime & lastModified() const { return m_lastModified; }
153     bool hadLastModified() const { return m_hadLastModified; }
154
155     const DetailMappingList & mappings() const { return m_mappings; }
156     const QContactLocalId & contactLocalId() const { return m_contactLocalId; }
157     const QTrackerContactDetailSchema & schema() const { return m_schema; }
158     const QTrackerClassHierarchy & classes() const { return m_classes; }
159
160     bool isSelfContact() const { return m_isSelfContact; }
161
162 public:
163     void collectInsertions(RDFUpdate &update);
164     void collectDeletions(RDFUpdate &update) const;
165
166 protected:
167     QUrl makeIri(QTrackerContactSubject::Scheme scheme, const QVariantList &values) const;
168     QString makeUniqueName(const QString &basename) const;
169
170     void insert(const RDFVariable &subject, const RDFVariable &predicate,
171                 const RDFVariable &value, bool explicitValue = true);
172
173     void insertCustomValues(const RDFVariable &subject, const QString &detailName,
174                             const QString &fieldName, const QVariant &value,
175                             const QVariantList &allowableValues = QVariantList());
176     void insertCustomValues(const RDFVariable &subject, const QString &detailName,
177                             const QTrackerContactDetailField &field, const QVariant &value);
178
179     void insertCustomDetail(RDFVariable &subject, const DetailMapping &detail);
180
181     void insertDetailField(QContactLocalId contactId,
182                            const RDFVariable &subject, const DetailMapping &detail,
183                            const QTrackerContactDetailField &field, const QVariant &value,
184                            const EntityHash &entities, PredicateVariableHash &objectCache);
185
186     void deleteContactProperties(RDFUpdate &update) const;
187     void deleteAssociatedObjects(RDFUpdate &update) const;
188     void deleteCustomDetails(RDFUpdate &update) const;
189
190     const QctLogger & qctLogger() const { return m_request->qctLogger(); }
191
192 private:
193     const QTrackerContactSaveRequest *const m_request;
194
195     QString m_guid;
196     QDateTime m_accessed;
197     QDateTime m_created;
198     QDateTime m_lastModified;
199     DetailMappingList m_mappings;
200     QContactLocalId m_contactLocalId;
201     const QTrackerContactDetailSchema &m_schema;
202     const QTrackerClassHierarchy &m_classes;
203
204     RDFStatementList m_explicitInserts;
205     RDFStatementList m_implicitInserts;
206
207     bool m_hadGuid : 1;
208     bool m_hadAccessed : 1;
209     bool m_hadCreated : 1;
210     bool m_hadLastModified : 1;
211     bool m_isSelfContact : 1;
212 };
213
214 ///////////////////////////////////////////////////////////////////////////////////////////////////
215
216 class QTrackerContactSaveRequestTask : public QObject
217 {
218     Q_DISABLE_COPY(QTrackerContactSaveRequestTask);
219     Q_OBJECT;
220
221 public:
222     QTrackerContactSaveRequestTask(const QDBusPendingCall &call, int offset, QObject *parent = 0);
223
224 signals:
225     void success(int offset);
226     void failure(int offset, QString const &message);
227
228 private slots:
229     void finished();
230
231 private:
232     QDBusPendingCallWatcher m_call;
233     const int m_offset;
234 };
235
236 ///////////////////////////////////////////////////////////////////////////////////////////////////
237
238 template <typename T>
239 inline bool operator<(const QList<T> &a, const QList<T> &b)
240 {
241     for(int i = 0; i < a.count() && i < b.count(); ++i) {
242         if (a[i] < b[i]) {
243             return true;
244         } else if (b[i] < a[i]) {
245             return false;
246         }
247     }
248
249     return a.count() < b.count();
250 }
251
252 ///////////////////////////////////////////////////////////////////////////////////////////////////
253
254 inline bool
255 DetailMapping::isCustomDetail() const
256 {
257     return (0 == d->m_trackerDetail);
258 }
259
260 inline bool
261 DetailMapping::isSyntheticDetail() const
262 {
263     return (0 != d->m_trackerDetail && d->m_trackerDetail->isSynthetic());
264 }
265
266 inline bool
267 DetailMapping::isAffiliatedDetail() const
268 {
269     return (d->m_trackerDetail->hasContext() &&
270             d->m_genericDetail.contexts().contains(QContactDetail::ContextWork));
271 }
272
273 inline bool
274 DetailMapping::isPersonalDetail() const
275 {
276     if (not d->m_trackerDetail->hasContext()) {
277         return true;
278     }
279
280     const QStringList contexts = d->m_genericDetail.contexts();
281
282     return (contexts.contains(QContactDetail::ContextHome) ||
283             not contexts.contains(QContactDetail::ContextWork));
284 }
285
286 inline QString
287 DetailMapping::definitionName() const
288 {
289     return d->m_genericDetail.definitionName();
290 }
291
292 inline QString
293 DetailMapping::detailUri() const
294 {
295     return d->m_genericDetail.detailUri();
296 }
297
298 inline QStringList
299 DetailMapping::linkedDetailUris() const
300 {
301     return d->m_genericDetail.linkedDetailUris();
302 }
303
304 inline QVariant
305 DetailMapping::fieldValue(const QString &fieldName) const
306 {
307     return d->m_genericDetail.variantValue(fieldName);
308 }
309
310 inline QVariantMap
311 DetailMapping::fieldValues() const
312 {
313     return d->m_genericDetail.variantValues();
314 }
315
316 inline const QList<QTrackerContactDetailField> &
317 DetailMapping::trackerFields() const
318 {
319     return d->m_trackerDetail->fields();
320 }
321
322 inline const QTrackerContactDetailField *
323 DetailMapping::subTypeField() const
324 {
325     return d->m_trackerDetail->subTypeField();
326 }
327
328 inline QTrackerContactSubject::Scheme
329 DetailMapping::detailScheme() const
330 {
331     return d->m_trackerDetail->detailScheme();
332 }
333
334 bool
335 DetailMapping::updateDetailUri(QContactLocalId contactId, const DetailMappingList &mappings)
336 {
337     // skip custom details
338     if (isCustomDetail()) {
339         return false;
340     }
341
342     QTrackerContactSubject::Scheme scheme = QTrackerContactSubject::Anonymous;
343     const QString oldDetailUri = detailUri();
344     QVariantList values;
345
346     // build new detail URI from current detail content
347     foreach(const QTrackerContactDetailField &f, trackerFields()) {
348         if (f.hasDetailUri()) {
349             if (values.isEmpty()) {
350                 scheme = f.propertyChain().last().subjectScheme();
351             }
352
353             values.append(fieldValue(f.name()));
354         }
355     }
356
357     // detail URI doesn't use field values, abort
358     if (values.isEmpty()) {
359         return false;
360     }
361
362     // construct new detail URI
363     const QUrl subjectIri = QTrackerContactSubject::makeIri(scheme, contactId, values);
364     const QString newDetailUri = subjectIri.toString();
365
366     // abort if nothing changed
367     if (newDetailUri == oldDetailUri) {
368         return false;
369     }
370
371     // update detail URI
372     d->m_genericDetail.setDetailUri(newDetailUri);
373
374     // update details pointing to this one
375     foreach(const DetailMapping &otherDetail, mappings) {
376         QStringList linkedDetails = otherDetail.linkedDetailUris();
377
378         // try to remove old URI from list of linked details
379         if (linkedDetails.removeOne(oldDetailUri)) {
380             // detail was linked, therefore update the link
381             otherDetail.d->m_genericDetail.setLinkedDetailUris(linkedDetails << newDetailUri);
382         }
383     }
384
385     return true;
386 }
387
388 ////////////////////////////////////////////////////////////////////////////////////////////////////
389
390 QTrackerContactSaveRequestTask::QTrackerContactSaveRequestTask(const QDBusPendingCall &call,
391                                                                int offset, QObject *parent)
392     : QObject(parent)
393     , m_call(call)
394     , m_offset(offset)
395 {
396     connect(&m_call, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(finished()));
397 }
398
399 void
400 QTrackerContactSaveRequestTask::finished()
401 {
402     if (m_call.isError()) {
403         emit failure(m_offset, m_call.error().message());
404     } else {
405         emit success(m_offset);
406     }
407 }
408
409 ///////////////////////////////////////////////////////////////////////////////////////////////////
410
411 QTrackerContactSaveRequest::QTrackerContactSaveRequest(QContactAbstractRequest *request,
412                                                        QContactTrackerEngine *engine,
413                                                        QObject *parent) :
414     QTrackerBaseRequest<QContactSaveRequest>(request, engine, parent),
415     m_timestamp(QDateTime::currentDateTime()),
416     m_effectiveError(QContactManager::UnspecifiedError),
417     m_contactOffset(0), m_batchSize(0)
418 {
419     m_contacts.append(m_request->contacts());
420 }
421
422 QTrackerContactSaveRequest::~QTrackerContactSaveRequest()
423 {
424 }
425
426 ///////////////////////////////////////////////////////////////////////////////////////////////////
427
428 static void
429 addEntity(const QTrackerClassHierarchy &classes, EntityHash &entities,
430           const PredicateChain &predicates, const QUrl &newIri)
431 {
432     foreach(const QUrl *const oldIri, entities.values(predicates)) {
433         if (classes.isSubClassOf(*oldIri, newIri)) {
434             // skipping already covered entity type
435             return;
436         }
437
438         if (classes.isSubClassOf(newIri, *oldIri)) {
439             // removing too generic entity type which is covered by newIri
440             entities.remove(predicates, oldIri);
441         }
442     }
443
444     // adding the new entity type
445     entities.insertMulti(predicates, &newIri);
446 }
447
448 static EntityHash
449 collectEntities(const QTrackerClassHierarchy &classes, const DetailMapping &detail)
450 {
451     EntityHash entities;
452
453     foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
454         if (field.propertyChain().isEmpty()) {
455             continue;
456         }
457
458         PredicateChain predicates;
459
460         const PropertyInfoList::ConstIterator end(field.propertyChain().end() - 1);
461         for(PropertyInfoList::ConstIterator i(field.propertyChain().begin()); i != end; ++i) {
462             const QUrl &predicateIri(i->iri());
463             const PropertyInfoBase &pi(*(i + 1));
464
465             if (pi.inverse()) {
466                 addEntity(classes, entities, predicates << predicateIri, pi.rangeIri());
467             } else {
468                 addEntity(classes, entities, predicates << predicateIri, pi.domainIri());
469             }
470         }
471
472         if (field.hasSubTypeClasses()) {
473             QVariant subTypeValue(detail.fieldValue(field.name()));
474             QSet<QString> subTypes;
475
476             if (subTypeValue.isNull() && not field.defaultValue().isNull()) {
477                 subTypeValue = field.defaultValue();
478             }
479
480             switch(subTypeValue.type()) {
481             case QVariant::String:
482                 subTypes.insert(subTypeValue.toString());
483                 break;
484
485             case QVariant::StringList:
486                 subTypes.unite(subTypeValue.toStringList().toSet());
487                 break;
488
489             default:
490                 qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
491                         arg(subTypeValue.typeName(), detail.definitionName(), field.name()));
492                 continue;
493             }
494
495             predicates.append(field.propertyChain().last().iri());
496
497             foreach(const ClassInfoBase &i, field.subTypeClasses()) {
498                 if (subTypes.contains(i.text())) {
499                     addEntity(classes, entities, predicates, i.iri());
500                 }
501             }
502         }
503     }
504
505     return entities;
506 }
507
508 static QVariant
509 findSubTypeValue(const DetailMapping &detail, const QTrackerContactDetailField *const subTypeField)
510 {
511     QVariant value;
512
513     if (0 != subTypeField && subTypeField->hasSubTypeProperties()) {
514         value = detail.fieldValue(subTypeField->name());
515
516         if (QVariant::String != value.type()) {
517             qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
518                     arg(value.typeName(), detail.definitionName(), subTypeField->name()));
519             value = QVariant();
520         }
521     }
522
523     return value;
524
525 }
526
527 static const QUrl &
528 findSubTypePredicate(const DetailMapping &detail, const QTrackerContactDetailField &field,
529                      const QTrackerContactDetailField *const subTypeField)
530 {
531     const QVariant subTypeValue = findSubTypeValue(detail, subTypeField);
532
533     if (subTypeValue.isValid()) {
534         foreach(const PropertyInfoBase &pi, subTypeField->subTypeProperties()) {
535             if (pi.value() == subTypeValue) {
536                 return pi.iri();
537             }
538         }
539     }
540
541     return field.propertyChain().last().iri();
542 }
543
544 QString
545 UpdateBuilder::makeUniqueName(const QString &basename) const
546 {
547     return basename + QString::number(m_explicitInserts.count());
548 }
549
550 void
551 UpdateBuilder::insertCustomValues(const RDFVariable &subject,
552                                   const QString &detailName, const QString &fieldName,
553                                   const QVariant &value, const QVariantList &allowableValues)
554 {
555     QVariantList customValues;
556
557     switch(value.type()) {
558     case QVariant::StringList:
559         foreach(const QString &element, value.toStringList()) {
560             if (not element.isEmpty() && not allowableValues.contains(element)) {
561                 customValues.append(element);
562             }
563         }
564
565         break;
566
567     case QVariant::List:
568         foreach(const QVariant &element, value.toList()) {
569             if (not element.isNull() && not allowableValues.contains(element)) {
570                 customValues.append(element);
571             }
572         }
573
574         break;
575
576     default:
577         if (not value.isNull() && not allowableValues.contains(value)) {
578             customValues.append(value);
579         }
580     }
581
582     foreach(const QVariant &element, customValues) {
583         RDFVariable property(makeUniqueName(QTrackerContactQueryBuilder::name(detailName, fieldName)));
584
585         insert(subject, rdf::type::iri(), nie::InformationElement::iri());
586         insert(subject, nao::hasProperty::iri(), property);
587
588         insert(property, rdf::type::iri(), nao::Property::iri());
589         insert(property, nao::propertyName::iri(), LiteralValue(fieldName));
590         insert(property, nao::propertyValue::iri(), QTrackerContactQueryBuilder::value(element));
591     }
592 }
593
594 void
595 UpdateBuilder::insertCustomValues(const RDFVariable &subject, const QString &detailName,
596                                   const QTrackerContactDetailField &field,
597                                   const QVariant &value)
598 {
599     insertCustomValues(subject, detailName, field.name(), value,
600                        field.describeAllowableValues());
601 }
602
603 static PropertyInfoList::ConstIterator
604 endOfPropertyChain(const QTrackerContactDetailField &field)
605 {
606     if (field.isWithoutMapping()) {
607         return field.propertyChain().end();
608     }
609
610     return field.propertyChain().end() - 1;
611 }
612
613 void
614 UpdateBuilder::insertDetailField(QContactLocalId contactId,
615                                  const RDFVariable &subject, const DetailMapping &detail,
616                                  const QTrackerContactDetailField &field, const QVariant &value,
617                                  const EntityHash &entities, PredicateVariableHash &objectCache)
618 {
619     if (not value.isValid()) {
620         qctWarn(QString::fromLatin1("Value must be valid for %2 field of %1 detail").
621                 arg(detail.definitionName(), field.name()));
622         return;
623     }
624
625     const QTrackerContactDetailField *const subTypeField = detail.subTypeField();
626     const PropertyInfoList &propertyChain = field.propertyChain();
627
628     if (propertyChain.isEmpty()) {
629         qctWarn(QString::fromLatin1("RDF property chain needed for %2 field of %1 detail").
630                 arg(detail.definitionName(), field.name()));
631         return;
632     }
633
634     if (propertyChain.first().readOnly()) {
635         return;
636     }
637
638     PredicateChain predicateChain;
639     RDFVariableList axis;
640     axis.append(subject);
641
642     const PropertyInfoList::ConstIterator end = endOfPropertyChain(field);
643     const PropertyInfoList::ConstIterator tail = propertyChain.end() - 1;
644
645     for(PropertyInfoList::ConstIterator pi = propertyChain.begin(); pi != end; ++pi) {
646         if (pi->readOnly()) {
647             break;
648         }
649
650         PredicateVariableHash::ConstIterator object = objectCache.find(predicateChain << pi->iri());
651
652         if (object == objectCache.end()) {
653             // figure out subject IRI for this field
654             QUrl subjectIri;
655
656             const PropertyInfoList::ConstIterator npi = pi + 1;
657
658             if (npi != propertyChain.end() && npi->hasSubjectScheme()) {
659                 // Prefer the detail's explicit URI for tail nodes, but only if the
660                 // property's subject scheme matches the detail's scheme. This additional
661                 // check is needed because details like Organization randomly spread their
662                 // fields over many different entities.
663                 if (npi == tail && npi->subjectScheme() == detail.detailScheme()) {
664                     subjectIri = detail.detailUri();
665                 }
666
667                 // If we don't have an entity IRI yet generate it from property and value.
668                 if (subjectIri.isEmpty()) {
669                     subjectIri = npi->makeSubject(contactId, value);
670                 }
671             }
672
673             // name object variable and assign subject IRI
674             RDFVariable objectVariable;
675
676             if (subjectIri.isEmpty()) {
677                 // create named blank variable if no subject IRI could be build
678                 const QString name = makeUniqueName(detail.definitionName());
679                 objectVariable.metaAssign(RDFVariable(name));
680             } else {
681                 // assign generated subject IRI
682                 objectVariable.metaAssign(subjectIri);
683             }
684
685             // assign RDF classes to this field
686             EntityHash::ConstIterator entity = entities.find(predicateChain);
687
688             if (entities.end() == entity) {
689                 qctWarn(QString::fromLatin1("Cannot find entities for %3 property of %1 detail's %2 field").
690                         arg(detail.definitionName(), field.name(), predicateChain.last().toString()));
691             }
692
693             for(; entity != entities.end() && entity.key() == predicateChain; ++entity) {
694                 // create insert statement of this entity type
695                 object = objectCache.insert(predicateChain, objectVariable);
696                 insert(*object, rdf::type::iri(), **entity);
697             }
698         }
699
700         // create insert statement with the field's value
701         insert(axis.last(), pi->iri(), *object);
702         axis.append(m_explicitInserts.last().object());
703
704         // insert custom values for subTypes field
705         if (subTypeField && subTypeField->permitsCustomValues()) {
706             insertCustomValues(axis.last(), detail.definitionName(), *subTypeField,
707                                detail.fieldValue(subTypeField->name()));
708         }
709     }
710
711     // find proper type of the value to insert
712     if (not field.isWithoutMapping()) {
713         const QUrl subTypePredicate = findSubTypePredicate(detail, field, subTypeField);
714         insert(axis.last(), subTypePredicate, QTrackerContactQueryBuilder::value(value));
715     }
716
717     // insert computed values
718     foreach(const PropertyInfoBase &pi, field.computedProperties()) {
719         QVariant computedValue;
720
721         if (pi.conversion()->makeValue(value, computedValue)) {
722             insert(axis.last(), pi.iri(), QTrackerContactQueryBuilder::value(computedValue));
723         }
724     }
725
726     // store custom values
727     if (field.permitsCustomValues()) {
728         insertCustomValues(axis.last(), detail.definitionName(), field, value);
729     }
730 }
731
732 UpdateBuilder::UpdateBuilder(const QTrackerContactSaveRequest *request,
733                              const QContact &contact, const QDateTime &timestamp)
734     : m_request(request)
735     , m_schema(request->engine()->schema(contact.type()))
736     , m_classes(request->engine()->classes())
737 {
738     // figure out the contact's timestamps
739    QContactTimestamp timestampDetail(contact.detail<QContactTimestamp>());
740
741    m_accessed = timestampDetail.variantValue(QContactTimestamp__FieldAccessedTimestamp).toDateTime();
742    m_hadAccessed = not m_accessed.isNull();
743
744    if (m_accessed.isNull()) {
745        m_accessed = timestamp;
746    }
747
748    m_created = timestampDetail.created();
749    m_hadCreated = not m_created.isNull();
750
751    if (m_created.isNull()) {
752        m_created = timestamp;
753    }
754
755    m_lastModified = timestampDetail.lastModified();
756    m_hadLastModified = not m_lastModified.isNull();
757
758    if (m_lastModified.isNull() || 0 != contact.localId()) {
759        // only preserve the lastModified field for new contacts...
760        m_lastModified = timestamp;
761    }
762
763    // figure out the contact's GUID
764    QContactGuid guidDetail(contact.detail<QContactGuid>());
765
766    m_guid = guidDetail.guid();
767    m_hadGuid = not m_guid.isEmpty();
768
769    if (m_guid.isEmpty()) {
770        m_guid = qctUuidString();
771    }
772
773    // calculate local contact ID
774    m_contactLocalId = contact.localId();
775
776    if (0 == m_contactLocalId) {
777        m_contactLocalId = qHash(guid());
778    }
779
780    m_isSelfContact = (m_contactLocalId == request->engine()->selfContactId(0));
781
782    // collect details and update their URIs
783    foreach(const QContactDetail &detail, contact.details()) {
784         const QTrackerContactDetail *definition(m_schema.detail(detail.definitionName()));
785
786         if (0 == definition) {
787             if (m_schema.isSyntheticDetail(detail.definitionName())) {
788                 continue; // don't even store synthesized details as nao:Property
789             }
790         }
791
792         m_mappings.append(DetailMapping(detail, definition));
793     }
794
795    foreach(DetailMapping detail, m_mappings) {
796        detail.updateDetailUri(contactLocalId(), m_mappings);
797    }
798 }
799
800 void
801 UpdateBuilder::insert(const RDFVariable &subject, const RDFVariable &predicate,
802                       const RDFVariable &value, bool explicitValue)
803 {
804     if (explicitValue) {
805         m_explicitInserts += RDFStatement(subject, predicate, value);
806     } else {
807         RDFVariable restricted = subject.metaValue().iri();
808         RDFVariable property = restricted.optional().property(predicate);
809
810         m_implicitInserts += RDFStatement(restricted, predicate, value);
811
812         not restricted.variable(property).isBound();
813     }
814 }
815
816 void
817 UpdateBuilder::insertCustomDetail(RDFVariable &subject, const DetailMapping &detail)
818 {
819     const QString detailName = detail.definitionName();
820     const QVariantMap fields = detail.fieldValues();
821
822     RDFVariable detailProperty(makeUniqueName(detailName));
823
824     insert(subject, nao::hasProperty::iri(), detailProperty);
825     insert(detailProperty, rdf::type::iri(), nao::Property::iri());
826     insert(detailProperty, nao::propertyName::iri(), LiteralValue(detailName));
827
828     for(QVariantMap::ConstIterator i = fields.begin(); i != fields.end(); ++i) {
829         insertCustomValues(detailProperty, detailName, i.key(), i.value());
830     }
831 }
832
833 void
834 UpdateBuilder::collectInsertions(RDFUpdate &update)
835 {
836     RDFVariable affilination;
837
838     // Removing Role properties also removed the contact's PersonContact type,
839     // so we have to restore it.
840     RDFVariable contact = schema().makeContactIri(contactLocalId());
841
842     // Restore the resource type, we removed it for quick propertly deletion.
843     foreach(const QUrl &classIri, schema().contactClassIris()) {
844         insert(contact, rdf::type::iri(), classIri);
845     }
846
847     // Always update the local UID to support a-priori contacts like the self contact.
848     // Accept update errors if that property already exists with different value as
849     // this property is assumed to be immutable.
850     const LiteralValue contactLocalUID(QString::number(contactLocalId()));
851     insert(contact, nco::contactLocalUID::iri(), contactLocalUID);
852
853     // Collect inserts for each regular detail
854     foreach(const DetailMapping &detail, mappings()) {
855         // store custom details via nao:Property
856         if (detail.isCustomDetail()) {
857             insertCustomDetail(contact, detail);
858             continue;
859         }
860
861         // don't save synthesized details
862         if (detail.isSyntheticDetail()) {
863             continue;
864         }
865
866         const QVariantMap detailValues = detail.fieldValues();
867         const EntityHash entities = collectEntities(classes(), detail);
868         const bool isAffiliatedDetail = detail.isAffiliatedDetail();
869         const bool isPersonalDetail = detail.isPersonalDetail();
870         PredicateVariableHash objectCache;
871
872         foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
873             if (field.hasSubTypes()) {
874                 // Subtypes are stored indirectly by assigning the proper resource class
875                 // to the RDF resource representing this field, or by selecting the matching
876                 // RDF property for storing the detail value.
877                 continue;
878             }
879
880             const QVariantMap::ConstIterator fieldValue = detailValues.find(field.name());
881
882             QVariant rdfValue;
883
884             if (fieldValue == detailValues.end() ||
885                 not field.makeValue(*fieldValue, rdfValue)) {
886                 // Could not compute the RDF value for this field.
887                 continue;
888             }
889
890             if (isPersonalDetail) {
891                 insertDetailField(contactLocalId(), contact, detail, field,
892                                   rdfValue, entities, objectCache);
893             }
894
895             if (isAffiliatedDetail) {
896                 // create nco::Affiliation entity when needed
897                 if (not affilination.metaIsDefinite()) {
898                     affilination.metaAssign(makeAffiliationIri(contactLocalId()));
899
900                     insert(affilination, rdf::type::iri(), nco::Affiliation::iri());
901                     insert(contact, nco::hasAffiliation::iri(), affilination);
902                 }
903
904                 insertDetailField(contactLocalId(), affilination, detail, field,
905                                   rdfValue, entities, objectCache);
906             }
907         }
908     }
909
910     insert(contact, nie::contentLastModified::iri(), LiteralValue(lastModified()));
911     insert(contact, nie::contentCreated::iri(), LiteralValue(created()), hadCreated());
912     insert(contact, nco::contactUID::iri(), LiteralValue(guid()), hadGuid());
913
914     update.addInsertion(m_explicitInserts);
915
916     foreach(const RDFStatement &statement, m_implicitInserts) {
917         update.addInsertion(statement);
918     }
919 }
920
921 void
922 UpdateBuilder::deleteContactProperties(RDFUpdate &update) const
923 {
924     RDFVariable contact = schema().makeContactIri(contactLocalId());
925
926     RDFVariable domain(QString::fromLatin1("d"));
927     RDFVariable predicate(QString::fromLatin1("p"));
928     RDFVariable object(QString::fromLatin1("o"));
929
930     // Only remove properties of inherited classes in managed ontologies
931     domain.isMemberOf(iriVariableList(schema().inheritedClassIris()));
932
933     predicate.property<rdfs::domain>(domain);
934     predicate.notEqual(nco::contactLocalUID::iri());
935
936     // Remove all properties of Role, Contact and PersonContact domain,
937     // except for local id and globally unique id.
938     if (not hadGuid()) {
939         predicate.notEqual(nco::contactUID::iri());
940     }
941
942     // NB#177560 - skip removing nco::IMAddress for self Contact
943     if (isSelfContact()) {
944        predicate.notEqual(nco::hasIMAddress::iri());
945     }
946
947     update.addDeletion(contact, predicate, object);
948
949     // Remove timestamps when needed.
950     if (hadAccessed()) {
951         update.addDeletion(contact, nie::contentAccessed::iri());
952     }
953
954     if (hadCreated()) {
955         update.addDeletion(contact, nie::contentCreated::iri());
956     }
957
958     update.addDeletion(contact, nie::contentLastModified::iri());
959
960     // Remove tags
961     update.addDeletion(contact, nao::hasTag::iri());
962 }
963
964 void
965 UpdateBuilder::deleteCustomDetails(RDFUpdate &update) const
966 {
967     RDFVariable contact = schema().makeContactIri(contactLocalId());
968     RDFVariable customDetail(QString::fromLatin1("customDetail"));
969     RDFVariable customField(QString::fromLatin1("customField"));
970
971     update.addDeletion(RDFStatementList() <<
972                        RDFStatement(customField, rdf::type::iri(), nao::Property::iri()) <<
973                        RDFStatement(customDetail, rdf::type::iri(), nao::Property::iri()) <<
974                        RDFStatement(customDetail, rdf::type::iri(), rdfs::Resource::iri()) <<
975                        RDFStatement(contact, nao::hasProperty::iri(), customDetail));
976
977     contact.property<nao::hasProperty>(customDetail);
978     customDetail.property<nao::hasProperty>(customField);
979 }
980
981 void
982 deleteCustomValues(RDFUpdate &update, const RDFVariable &resource,
983                    const QTrackerContactDetail &definition)
984 {
985     RDFVariableList customDetailFields;
986
987     foreach(const QTrackerContactDetailField &field, definition.fields()) {
988         if (field.permitsCustomValues()) {
989             customDetailFields += LiteralValue(field.name());
990         }
991     }
992
993
994     if (not customDetailFields.isEmpty()) {
995         RDFVariable customProperty = resource.property<nao::hasProperty>();
996         update.addDeletion(customProperty, rdf::type::iri(), nao::Property::iri());
997         customProperty.property<nao::propertyName>().isMemberOf(customDetailFields);
998     }
999 }
1000
1001 void
1002 UpdateBuilder::deleteAssociatedObjects(RDFUpdate &update) const
1003 {
1004     RDFVariable contact = schema().makeContactIri(contactLocalId());
1005
1006     // Remove all affiliation and organization properties in one go.
1007     // Initially we limited to nco:Role instead of rdf:Resource to only hit
1008     // "owned" properties, but this causes trouble with properties like
1009     // nco:fullname which for instance also implements dc:title.
1010     RDFVariable affiliation = makeAffiliationIri(contactLocalId());
1011     update.addDeletion(affiliation, rdf::type::iri(), rdfs::Resource::iri());
1012
1013     RDFVariable organization = makeOrganizationIri(contactLocalId());
1014     update.addDeletion(organization, rdf::type::iri(), rdfs::Resource::iri());
1015
1016     // Reset subtypes of associated entities
1017     foreach(const QTrackerContactDetail &definition, schema().details()) {
1018         const QTrackerContactDetailField *const subTypeField = definition.subTypeField();
1019         if (0 == subTypeField || not subTypeField->hasSubTypes()) {
1020             continue;
1021         }
1022
1023         const QTrackerContactDetailField *const subjectField = definition.subjectField();
1024
1025         if (0 == subjectField) {
1026             // There is no subject field for this detail.
1027             continue;
1028         }
1029
1030         const PropertyInfoList &subjectPropertyChain = subjectField->propertyChain();
1031
1032         if (subjectPropertyChain.count() < 2) {
1033             // There must be at least the property creating the association
1034             // and the property storing the value.
1035             continue;
1036         }
1037
1038         const PropertyInfoBase &subjectProperty = subjectPropertyChain[1];
1039         const QTrackerContactSubject::Scheme scheme = subjectProperty.domainScheme();
1040
1041         if (QTrackerContactSubject::isContentScheme(scheme)) {
1042             // delete associated object by its content IRI
1043             typedef QPair<QUrl, QUrl> IriPair;
1044             QList<IriPair> resourceIris;
1045
1046             foreach(const DetailMapping &mapping, mappings()) {
1047                 if (mapping.definitionName() != definition.name()) {
1048                     continue;
1049                 }
1050
1051                 const QVariant value = mapping.fieldValue(subjectField->name());
1052
1053                 if (value.isNull()) {
1054                     continue;
1055                 }
1056
1057                 resourceIris += qMakePair(makeIri(scheme, QVariantList() << value),
1058                                           subjectProperty.domainIri());
1059             }
1060
1061             foreach(const IriPair &iris, resourceIris) {
1062                 deleteCustomValues(update, iris.first, definition);
1063             }
1064
1065             foreach(const IriPair &iris, resourceIris) {
1066                 update.addDeletion(iris.first, rdf::type::iri(), iris.second);
1067             }
1068         } else {
1069             // no content IRI known, delete via association
1070             const PropertyInfoBase &associatingProperty = subjectPropertyChain[0];
1071             RDFVariable resource = associatingProperty.bindProperty(contact);
1072
1073             deleteCustomValues(update, resource.deepCopy(), definition);
1074             update.addDeletion(resource, rdf::type::iri(), rdfs::Resource::iri());
1075         }
1076     }
1077 }
1078
1079 void
1080 UpdateBuilder::collectDeletions(RDFUpdate &update) const
1081 {
1082     deleteContactProperties(update);
1083     deleteCustomDetails(update);
1084     deleteAssociatedObjects(update);
1085 }
1086
1087 QUrl
1088 UpdateBuilder::makeIri(QTrackerContactSubject::Scheme scheme, const QVariantList &values) const
1089 {
1090     return QTrackerContactSubject::makeIri(scheme, contactLocalId(), values);
1091 }
1092
1093 void
1094 QTrackerContactSaveRequest::normalizeCurrentContact()
1095 {
1096     QContact &contact = m_contacts[m_contactOffset];
1097
1098     foreach(const QTrackerContactDetail &detail, engine()->schema(contact.type()).details()) {
1099         if (not detail.isUnique()) {
1100             continue;
1101         }
1102
1103         QList<QContactDetail> contactDetails(contact.details(detail.name()));
1104
1105         if (contactDetails.count() < 2) {
1106             continue;
1107         }
1108
1109         qctWarn(QString::fromLatin1("Dropping odd details for contact %1: "
1110                                     "%2 detail must be unique").
1111                 arg(m_contactOffset).arg(detail.name()));
1112
1113         for(int i = 1; i < contactDetails.count(); ++i) {
1114             contact.removeDetail(&contactDetails[i]);
1115         }
1116     }
1117 }
1118
1119 static QString
1120 makeAvatarCacheFileName(const QImage& image)
1121 {
1122     // FIXME: H-Contacts needs to fix loading of images from URI
1123     // when not inside their own cache directory.
1124
1125     const QDir avatarCacheDir = qctAvatarCacheDir();
1126
1127     // Create avatar cache directory when needed.
1128
1129     if (not avatarCacheDir.mkpath(QLatin1String("."))) {
1130         qctWarn(QString::fromLatin1("Cannot create avatar cache folder %1: %2").
1131                 arg(avatarCacheDir.path(), strerror(errno)));
1132         return QString();
1133     }
1134
1135     // Create filename from cryptographic hash of the pixel data. Probability that two
1136     // different pictures on the same device have the same hash code, is significantly
1137     // smaller than probability that we mess up because of miscalculations caused by
1138     // electrostatics and flipping bits. That's why we can apply this cheap trick.
1139
1140     const QByteArray pixels(reinterpret_cast<const char *>(image.constBits()), image.byteCount());
1141     const QString pixelHash = QCryptographicHash::hash(pixels, QCryptographicHash::Sha1).toHex();
1142
1143     const QString fileName = QString::fromLatin1("%1.png").arg(pixelHash);
1144     return avatarCacheDir.absoluteFilePath(fileName);
1145 }
1146
1147 static QSet<QString>
1148 findAccountDetailUris(const QContact &contact)
1149 {
1150     QSet<QString> onlineAccountUris;
1151
1152     foreach(const QContactOnlineAccount &detail, contact.details<QContactOnlineAccount>()) {
1153         onlineAccountUris += detail.detailUri();
1154     }
1155
1156     return onlineAccountUris;
1157 }
1158
1159 static bool
1160 isLinkedDetail(const QContactDetail &detail, const QSet<QString> &linkedDetailUris)
1161 {
1162     foreach(const QString &uri, detail.linkedDetailUris()) {
1163         if (linkedDetailUris.contains(uri)) {
1164             return true;
1165         }
1166     }
1167
1168     return false;
1169 }
1170
1171 bool
1172 QTrackerContactSaveRequest::normalizeAvatars()
1173 {
1174     QContact &contact = m_contacts[m_contactOffset];
1175     const QSet<QString> accountDetailUris = findAccountDetailUris(contact);
1176
1177     // Find user supplied thumbnail of the contact itself.
1178     QContactThumbnail defaultThumbnail;
1179
1180     foreach(const QContactThumbnail &thumbnail, contact.details<QContactThumbnail>()) {
1181         if (not isLinkedDetail(thumbnail, accountDetailUris)) {
1182             defaultThumbnail = thumbnail;
1183             break;
1184         }
1185     }
1186
1187     if (defaultThumbnail.isEmpty()) {
1188         return true;
1189     }
1190
1191     // Find default avatar of the contact itself.
1192     QContactAvatar defaultAvatar;
1193
1194     foreach(const QContactAvatar &avatar, contact.details<QContactAvatar>()) {
1195         if (not isLinkedDetail(avatar, accountDetailUris)) {
1196             defaultAvatar = avatar;
1197             break;
1198         }
1199     }
1200
1201     // Get avatar thumbnail from detail and scale down when needed.
1202     const QSize avatarSize = QTrackerContactSettings().avatarSize();
1203     QImage thumbnailImage = defaultThumbnail.thumbnail();
1204
1205     if (thumbnailImage.width() > avatarSize.width() ||
1206         thumbnailImage.height() > avatarSize.height()) {
1207         thumbnailImage = thumbnailImage.scaled(avatarSize, Qt::KeepAspectRatio);
1208     }
1209
1210     // Save the avatar image when neeed. The cached file name is based on
1211     // a cryptographic hash of the thumbnail's pixels. So if there already
1212     // exists a image file with the calculated name, there is an incredibly
1213     // high probability the existing file is equal to this current avatar.
1214     // We prefer to not write the file in this case for better performance
1215     // and for better lifetime of the storage medium.
1216     const QString avatarCacheFileName = makeAvatarCacheFileName(thumbnailImage);
1217     bool avatarWritten = true;
1218
1219     if (avatarCacheFileName.isEmpty()) {
1220         avatarWritten = false; // failed to build file name
1221     } else if (not QFile::exists(avatarCacheFileName)) {
1222         QImageWriter writer(avatarCacheFileName);
1223
1224         avatarWritten = writer.write(thumbnailImage);
1225
1226         if (not avatarWritten) {
1227             qctWarn(QString::fromLatin1("Cannot save avatar thumbnail for contact %1: %2").
1228                     arg(m_contactOffset).arg(writer.errorString()));
1229         }
1230     }
1231
1232     if (avatarWritten) {
1233         // everything went find - update avatar detail and proceed
1234         defaultAvatar.setImageUrl(QUrl::fromLocalFile(avatarCacheFileName));
1235         contact.saveDetail(&defaultAvatar);
1236     } else if (not defaultAvatar.isEmpty()) {
1237         // failed to save avatar picture - remove avatar detail
1238         contact.removeDetail(&defaultAvatar);
1239     }
1240
1241     return avatarWritten;
1242 }
1243
1244 RDFUpdate
1245 QTrackerContactSaveRequest::buildQuery(QContact &contact)
1246 {
1247     UpdateBuilder builder(this, contact, timestamp());
1248
1249     // Create local contact id when needed
1250     QContactId id = contact.id();
1251     id.setLocalId(builder.contactLocalId());
1252     id.setManagerUri(engine()->managerUri());
1253     contact.setId(id);
1254
1255     // Build the update query
1256     RDFUpdate update;
1257
1258     builder.collectDeletions(update);
1259     builder.collectInsertions(update);
1260
1261     return update;
1262 }
1263
1264 ///////////////////////////////////////////////////////////////////////////////////////////////////
1265
1266 void
1267 QTrackerContactSaveRequest::saveContact()
1268 {
1269     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1270         qDebug()
1271                 << metaObject()->className() << m_stopWatch.elapsed()
1272                 << ": contact" << m_contactOffset << "- updating";
1273     }
1274
1275     // drop odd details
1276     normalizeCurrentContact();
1277
1278     // normalize and save avatar
1279     if (not normalizeAvatars()) {
1280         return;
1281     }
1282
1283     // build the update query and run it
1284     const RDFUpdate query = buildQuery(m_contacts[m_contactOffset]);
1285     const QString queryString = ::BackEnds::Tracker::tracker()->rawQueryString(query);
1286
1287     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowUpdates)) {
1288         qDebug() << queryString;
1289     }
1290
1291     QDBusPendingCall updateCall = QctTracker::Resources::
1292             batchSparqlUpdate(m_trackerInstance, queryString,
1293                               engine()->trackerTimeout());
1294
1295     QTrackerContactSaveRequestTask *pendingUpdate =
1296             new QTrackerContactSaveRequestTask(updateCall, m_contactOffset, this);
1297
1298     connect(pendingUpdate, SIGNAL(failure(int,QString)), SLOT(failure(int,QString)));
1299     connect(pendingUpdate, SIGNAL(success(int)), SLOT(success(int)));
1300     m_pendingTasks.insert(m_contactOffset, pendingUpdate);
1301
1302     if (++m_batchSize >= engine()->batchSize()) {
1303         commit();
1304     }
1305 }
1306
1307 void
1308 QTrackerContactSaveRequest::proceed()
1309 {
1310     // Too many updates are running still, let's abort.
1311     // We'll get called again when next pending update is commited.
1312     if (m_pendingTasks.count() >= engine()->concurrencyLevel() / 2 &&
1313         m_pendingTasks.count() > 0) {
1314         return;
1315     }
1316
1317     while (m_contactOffset < m_contacts.count()) {
1318         // Now we have queued too many updates, let's abort.
1319         // We'll get called again when next pending update is commited.
1320         if (m_pendingTasks.count() >= engine()->concurrencyLevel()) {
1321             return;
1322         }
1323
1324         saveContact();
1325         m_contactOffset++;
1326     }
1327
1328     if (hasPendingUpdates()) {
1329         commit(); // We left the saveContact() loop, and now must commit the queued updates.
1330         return;
1331     }
1332
1333     // We left the saveContact() loop, and don't have any pending updates or
1334     // pending commits. This shows the save request was finished. Let's report this.
1335     if (not hasPendingCommit()) {
1336         if (m_errorMap.isEmpty()) {
1337             m_effectiveError = QContactManager::NoError;
1338         }
1339
1340         emitResult(m_effectiveError);
1341     }
1342 }
1343
1344 void
1345 QTrackerContactSaveRequest::commit()
1346 {
1347     // Don't do anything if another commit is running still.
1348     if (hasPendingCommit()) {
1349         return;
1350     }
1351
1352     // Dump some debug information when requested.
1353     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1354         qDebug()
1355                 << metaObject()->className() << m_stopWatch.elapsed()
1356                 << "- commiting" << m_batchSize << "pending updates";
1357     }
1358
1359     // Tell tracker to commit queued updates.
1360     QDBusPendingCall commitCall = QctTracker::Resources::
1361             batchCommit(m_trackerInstance, engine()->trackerTimeout());
1362
1363     m_commit.reset(new QDBusPendingCallWatcher(commitCall));
1364
1365     connect(m_commit.data(),
1366             SIGNAL(finished(QDBusPendingCallWatcher*)),
1367             SLOT(commitFinished()));
1368
1369     m_batchSize = 0;
1370 }
1371
1372 void
1373 QTrackerContactSaveRequest::commitFinished()
1374 {
1375     if (m_commit->isError()) {
1376         // Seems we don't have to do anything for for this error except for printing
1377         // it to console: We won't get a success signal for the failed update, and
1378         // therefore the failed contact should stay in m_errorMap[] causing proper
1379         // error reporting.
1380         qctWarn(QString::fromLatin1("Commit failed: %1").arg(m_commit->error().message()));
1381     }
1382
1383
1384     m_commit.reset(); // Mark this commit as done.
1385     proceed(); // Check if other updates are pending, or if more contacts must be written.
1386 }
1387
1388 void
1389 QTrackerContactSaveRequest::success(int offset)
1390 {
1391     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1392         qDebug()
1393                 << metaObject()->className() << m_stopWatch.elapsed()
1394                 << ": contact" << offset << " - update succeded";
1395     }
1396
1397     delete m_pendingTasks.take(offset); // Mark this update as done.
1398     m_errorMap.remove(offset); // Mark this update's contact as successfully written.
1399     proceed(); // Check if other updates are pending, or if more contacts must be written.
1400 }
1401
1402 void
1403 QTrackerContactSaveRequest::failure(int offset, QString const &message)
1404 {
1405     qctWarn(QString::fromLatin1("Failed to update contact %1: %2").arg(offset).arg(message));
1406
1407     delete m_pendingTasks.take(offset); // Mark this update as done.
1408     proceed(); // Check if other updates are pending, or if more contacts must be written.
1409
1410     // Notice: We don't remove the update's contact from m_errorMap[]
1411     // which results in proper error reporting.
1412 }
1413
1414 ///////////////////////////////////////////////////////////////////////////////////////////////////
1415
1416 bool
1417 QTrackerContactSaveRequest::hasPendingUpdates() const
1418 {
1419     return not m_pendingTasks.isEmpty();
1420 }
1421
1422 bool
1423 QTrackerContactSaveRequest::hasPendingCommit() const
1424 {
1425     return not m_commit.isNull();
1426 }
1427
1428 ///////////////////////////////////////////////////////////////////////////////////////////////////
1429
1430 bool
1431 QTrackerContactSaveRequest::start()
1432 {
1433     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1434         qDebug()
1435                 << metaObject()->className() << 0
1436                 << ": number of contacts:" << m_request->contacts().count();
1437     }
1438
1439     m_stopWatch.start();
1440
1441     // prepare error map with default values
1442     for(int i = 0; i < m_contacts.count(); ++i) {
1443         m_errorMap.insert(i, QContactManager::UnspecifiedError);
1444     }
1445
1446     // find tracker instance
1447     m_trackerInstance = QctTracker::instance();
1448
1449     if (m_trackerInstance.isEmpty()) {
1450         qctWarn(QString::fromLatin1("tracker service not found: %1").arg(QctTracker::lastError));
1451         return false;
1452     }
1453
1454     // start operation
1455     m_contactOffset = 0;
1456     QTimer::singleShot(0, this, SLOT(proceed()));
1457
1458     return true;
1459 }
1460
1461 void
1462 QTrackerContactSaveRequest::emitResult(QContactManager::Error error)
1463 {
1464     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowTiming)) {
1465         qDebug()
1466                 << metaObject()->className()  << m_stopWatch.elapsed()
1467                 << ": reporting result" << error;
1468     }
1469
1470     engine()->updateContactSaveRequest(m_request, m_contacts, error, m_errorMap,
1471                                       QContactAbstractRequest::FinishedState);
1472 }
1473
1474 ///////////////////////////////////////////////////////////////////////////////////////////////////
1475
1476 #include "contactsaverequest.moc"