Changes: Remove leading underscore from variable names where possible.
[qtcontacts-tracker: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 <errno.h>
59
60 ///////////////////////////////////////////////////////////////////////////////////////////////////
61
62 using namespace SopranoLive;
63 using namespace SopranoLive::Ontologies;
64
65 ///////////////////////////////////////////////////////////////////////////////////////////////////
66
67 typedef QMap<QString, RDFVariable> AffiliationMap;
68 typedef QList<class ExplictObjectInfo> ExplictObjectInfoList;
69 typedef QMap<QString, ExplictObjectInfoList> ExplictObjectInfoListMap;
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         , m_contextsNormalized(false)
85     {
86     }
87
88 private: // fields
89     QContactDetail                  m_genericDetail;
90     const QTrackerContactDetail *   m_trackerDetail;
91     bool                            m_contextsNormalized : 1;
92 };
93
94 ///////////////////////////////////////////////////////////////////////////////////////////////////
95
96 class DetailMapping
97 {
98 public: // constructors
99     DetailMapping(const QContactDetail &genericDetail,
100                   const QTrackerContactDetail *trackerDetail)
101         : d(new DetailMappingData(genericDetail, trackerDetail))
102     {
103     }
104
105 public: // attributes
106     /// a detail which has no own ontology representation
107     bool isCustomDetail() const;
108
109     /// is a detail which is generated from other details and not stored itself
110     bool isSyntheticDetail() const;
111
112     QString definitionName() const;
113     QStringList contexts() const;
114
115     QString detailUri() const;
116     QStringList linkedDetailUris() const;
117
118     QVariant fieldValue(const QString &fieldName) const;
119     QVariantMap fieldValues() const;
120
121     const QList<QTrackerContactDetailField> & trackerFields() const;
122     const QTrackerContactDetailField * subTypeField() const;
123     QTrackerContactSubject::Scheme detailScheme() const;
124
125 public: // methods
126     QUrl makeSubjectIri(QContactLocalId contactId) const;
127     bool updateDetailUri(QContactLocalId contactId, const DetailMappingList &mappings);
128
129 private: // fields
130     QExplicitlySharedDataPointer<DetailMappingData> d;
131 };
132
133 ///////////////////////////////////////////////////////////////////////////////////////////////////
134
135 class ExplictObjectInfo
136 {
137 public: // constructor
138     ExplictObjectInfo(const QUrl &objectIri, const QTrackerContactDetail &detail)
139         : m_objectIri(objectIri.toString())
140         , m_hasCustomFields(not detail.customValueChains().isEmpty())
141     {
142     }
143
144 public: // attributes
145     const QString & objectIri() const { return m_objectIri; }
146     bool hasCustomFields() const { return m_hasCustomFields; }
147
148 private: // fields
149     QString m_objectIri;
150     bool m_hasCustomFields : 1;
151 };
152
153 ///////////////////////////////////////////////////////////////////////////////////////////////////
154
155 class UpdateBuilder
156 {
157 public: // constructors
158     UpdateBuilder(const QTrackerContactSaveRequest *request, const QContact &contact);
159
160 public: // attributes
161     const QString & guid() const { return m_guid; }
162     bool hadGuid() const { return m_hadGuid; }
163
164     const QDateTime & created() const { return m_created; }
165     bool hadCreated() const { return m_hadCreated; }
166
167     const QDateTime & accessed() const { return m_accessed; }
168     const QDateTime & lastModified() const { return m_lastModified; }
169
170     const DetailMappingList & mappings() const { return m_mappings; }
171     const QContactLocalId & contactLocalId() const { return m_contactLocalId; }
172     const QTrackerContactDetailSchema & schema() const { return m_schema; }
173     const QTrackerClassHierarchy & classes() const { return m_classes; }
174
175     bool isSelfContact() const { return m_isSelfContact; }
176
177     QString queryString();
178
179 protected: // attributes
180     const QctLogger & qctLogger() const { return m_logger; }
181
182 protected: // methods
183     void collectInsertions();
184
185     QString makeUniqueName(const QString &basename = QLatin1String("_"));
186
187     RDFVariable & lookupAffiliation(const QString &context);
188
189     void restrictAffiliation(const RDFVariable &contact,
190                              const RDFVariable &affiliation,
191                              const QString &context);
192
193     void insert(const RDFVariable &subject, const RDFVariable &predicate,
194                 const RDFVariable &value, bool explicitValue = true);
195
196     void insertCustomValues(const RDFVariable &subject, const QString &detailName,
197                             const QString &fieldName, const QVariant &value,
198                             const QVariantList &allowableValues = QVariantList());
199     void insertCustomValues(const RDFVariable &subject, const QString &detailName,
200                             const QTrackerContactDetailField &field, const QVariant &value);
201
202     void insertCustomDetail(RDFVariable &subject, const DetailMapping &detail);
203
204     void insertDetailField(QContactLocalId contactId,
205                            const RDFVariable &subject, const DetailMapping &detail,
206                            const QTrackerContactDetailField &field, const QVariant &value,
207                            const EntityHash &entities, PredicateVariableHash &objectCache);
208
209     void appendPredicateChain(const QString &prefix, const PredicateChain &chain,
210                               const QString &suffix, const QString &indent);
211
212     void collectRelatedObjects();
213
214     void deleteCustomProperties();
215     void deleteRelatedObjects();
216     void deleteContactProperties();
217
218 private: // fields
219     const QctLogger &m_logger;
220     const QTrackerContactDetailSchema &m_schema;
221     const QTrackerClassHierarchy &m_classes;
222     const QUrl &m_graphIri;
223
224     QString m_guid;
225     QDateTime m_accessed;
226     QDateTime m_created;
227     QDateTime m_lastModified;
228     DetailMappingList m_mappings;
229     QContactLocalId m_contactLocalId;
230     QString m_contactIri;
231
232     AffiliationMap m_affiliations;
233
234     bool m_hadGuid : 1;
235     bool m_hadCreated : 1;
236     bool m_isSelfContact : 1;
237
238     QList<QTrackerContactDetail> m_implictlyRelatedObjects;
239     ExplictObjectInfoListMap m_explicitlyRelatedObjects;
240
241     RDFStatementList m_explicitInsertStatements;
242     RDFStatementList m_implicitInsertStatements;
243
244     QString m_queryString;
245     int m_variableCounter;
246 };
247
248 ///////////////////////////////////////////////////////////////////////////////////////////////////
249
250 class QTrackerContactSaveRequestTask : public QObject
251 {
252     Q_DISABLE_COPY(QTrackerContactSaveRequestTask);
253     Q_OBJECT;
254
255 public:
256     QTrackerContactSaveRequestTask(const QDBusPendingCall &call, int offset, QObject *parent = 0);
257
258 signals:
259     void success(int offset);
260     void failure(int offset, QString const &message);
261
262 private slots:
263     void finished();
264
265 private:
266     QDBusPendingCallWatcher m_call;
267     const int m_offset;
268 };
269
270 ///////////////////////////////////////////////////////////////////////////////////////////////////
271
272 template <typename T>
273 inline bool operator<(const QList<T> &a, const QList<T> &b)
274 {
275     for(int i = 0; i < a.count() && i < b.count(); ++i) {
276         if (a[i] < b[i]) {
277             return true;
278         } else if (b[i] < a[i]) {
279             return false;
280         }
281     }
282
283     return a.count() < b.count();
284 }
285
286 ///////////////////////////////////////////////////////////////////////////////////////////////////
287
288 inline bool
289 DetailMapping::isCustomDetail() const
290 {
291     return (0 == d->m_trackerDetail);
292 }
293
294 inline bool
295 DetailMapping::isSyntheticDetail() const
296 {
297     return (0 != d->m_trackerDetail && d->m_trackerDetail->isSynthetic());
298 }
299
300 inline QStringList
301 DetailMapping::contexts() const
302 {
303     if (0 != d->m_trackerDetail && not d->m_trackerDetail->hasContext()) {
304         return QStringList();
305     }
306
307     if (not d->m_contextsNormalized) {
308         QStringList normalizedContexts;
309
310         foreach(const QString &context, d->m_genericDetail.contexts().toSet()) {
311             normalizedContexts += qctCamelCase(context);
312         }
313
314         d->m_genericDetail.setContexts(normalizedContexts);
315         d->m_contextsNormalized = true;
316     }
317
318     return d->m_genericDetail.contexts();
319 }
320
321 inline QString
322 DetailMapping::definitionName() const
323 {
324     return d->m_genericDetail.definitionName();
325 }
326
327 inline QString
328 DetailMapping::detailUri() const
329 {
330     return d->m_genericDetail.detailUri();
331 }
332
333 inline QStringList
334 DetailMapping::linkedDetailUris() const
335 {
336     return d->m_genericDetail.linkedDetailUris();
337 }
338
339 inline QVariant
340 DetailMapping::fieldValue(const QString &fieldName) const
341 {
342     return d->m_genericDetail.variantValue(fieldName);
343 }
344
345 inline QVariantMap
346 DetailMapping::fieldValues() const
347 {
348     return d->m_genericDetail.variantValues();
349 }
350
351 inline const QList<QTrackerContactDetailField> &
352 DetailMapping::trackerFields() const
353 {
354     return d->m_trackerDetail->fields();
355 }
356
357 inline const QTrackerContactDetailField *
358 DetailMapping::subTypeField() const
359 {
360     return d->m_trackerDetail->subTypeField();
361 }
362
363 inline QTrackerContactSubject::Scheme
364 DetailMapping::detailScheme() const
365 {
366     return d->m_trackerDetail->detailScheme();
367 }
368
369 QUrl
370 DetailMapping::makeSubjectIri(QContactLocalId contactId) const
371 {
372     QTrackerContactSubject::Scheme scheme = QTrackerContactSubject::None;
373     QVariantList values;
374
375     // build new detail URI from current detail content
376     foreach(const QTrackerContactDetailField &field, trackerFields()) {
377         if (not field.hasDetailUri()) {
378             continue;
379         }
380
381         // don't mess with read only fields
382         if (field.propertyChain().first().readOnly()) {
383             continue;
384         }
385
386         // get schema from very first detail URI field
387         if (values.isEmpty()) {
388             scheme = field.propertyChain().last().subjectScheme();
389         }
390
391         // collect value of this detail URI field
392         values.append(fieldValue(field.name()));
393     }
394
395     // detail URI doesn't use field values, abort
396     if (values.isEmpty()) {
397         return QUrl();
398     }
399
400     return QTrackerContactSubject::makeIri(scheme, contactId, values);
401 }
402
403 bool
404 DetailMapping::updateDetailUri(QContactLocalId contactId, const DetailMappingList &mappings)
405 {
406     // skip custom details
407     if (isCustomDetail()) {
408         return false;
409     }
410
411     const QString oldDetailUri = detailUri();
412     const QString newDetailUri = makeSubjectIri(contactId).toString();
413
414     // abort if nothing to see
415     if (newDetailUri.isEmpty() || newDetailUri == oldDetailUri) {
416         return false;
417     }
418
419     // update detail URI
420     d->m_genericDetail.setDetailUri(newDetailUri);
421
422     // update details pointing to this one
423     foreach(const DetailMapping &otherDetail, mappings) {
424         QStringList linkedDetails = otherDetail.linkedDetailUris();
425
426         // try to remove old URI from list of linked details
427         if (linkedDetails.removeOne(oldDetailUri)) {
428             // detail was linked, therefore update the link
429             otherDetail.d->m_genericDetail.setLinkedDetailUris(linkedDetails << newDetailUri);
430         }
431     }
432
433     return true;
434 }
435
436 ////////////////////////////////////////////////////////////////////////////////////////////////////
437
438 QTrackerContactSaveRequestTask::QTrackerContactSaveRequestTask(const QDBusPendingCall &call,
439                                                                int offset, QObject *parent)
440     : QObject(parent)
441     , m_call(call)
442     , m_offset(offset)
443 {
444     connect(&m_call, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(finished()));
445 }
446
447 void
448 QTrackerContactSaveRequestTask::finished()
449 {
450     if (m_call.isError()) {
451         emit failure(m_offset, m_call.error().message());
452     } else {
453         emit success(m_offset);
454     }
455 }
456
457 ///////////////////////////////////////////////////////////////////////////////////////////////////
458
459 QTrackerContactSaveRequest::QTrackerContactSaveRequest(QContactAbstractRequest *request,
460                                                        QContactTrackerEngine *engine,
461                                                        QObject *parent) :
462     QTrackerBaseRequest<QContactSaveRequest>(request, engine, parent),
463     m_timestamp(QDateTime::currentDateTime()),
464     m_effectiveError(QContactManager::UnspecifiedError),
465     m_contactOffset(0), m_batchSize(0)
466 {
467     m_contacts.append(m_request->contacts());
468 }
469
470 QTrackerContactSaveRequest::~QTrackerContactSaveRequest()
471 {
472 }
473
474 ///////////////////////////////////////////////////////////////////////////////////////////////////
475
476 static void
477 addEntity(const QTrackerClassHierarchy &classes, EntityHash &entities,
478           const PredicateChain &predicates, const QUrl &newIri)
479 {
480     foreach(const QUrl *const oldIri, entities.values(predicates)) {
481         if (classes.isSubClassOf(*oldIri, newIri)) {
482             // skipping already covered entity type
483             return;
484         }
485
486         if (classes.isSubClassOf(newIri, *oldIri)) {
487             // removing too generic entity type which is covered by newIri
488             entities.remove(predicates, oldIri);
489         }
490     }
491
492     // adding the new entity type
493     entities.insertMulti(predicates, &newIri);
494 }
495
496 static EntityHash
497 collectEntities(const QTrackerClassHierarchy &classes, const DetailMapping &detail)
498 {
499     EntityHash entities;
500
501     foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
502         if (field.propertyChain().isEmpty()) {
503             continue;
504         }
505
506         PredicateChain predicates;
507
508         const PropertyInfoList::ConstIterator end(field.propertyChain().end() - 1);
509         for(PropertyInfoList::ConstIterator i(field.propertyChain().begin()); i != end; ++i) {
510             const QUrl &predicateIri(i->iri());
511             const PropertyInfoBase &pi(*(i + 1));
512
513             if (pi.inverse()) {
514                 addEntity(classes, entities, predicates << predicateIri, pi.rangeIri());
515             } else {
516                 addEntity(classes, entities, predicates << predicateIri, pi.domainIri());
517             }
518         }
519
520         if (field.hasSubTypeClasses()) {
521             QVariant subTypeValue(detail.fieldValue(field.name()));
522             QSet<QString> subTypes;
523
524             if (subTypeValue.isNull() && not field.defaultValue().isNull()) {
525                 subTypeValue = field.defaultValue();
526             }
527
528             switch(subTypeValue.type()) {
529             case QVariant::String:
530                 subTypes.insert(subTypeValue.toString());
531                 break;
532
533             case QVariant::StringList:
534                 subTypes.unite(subTypeValue.toStringList().toSet());
535                 break;
536
537             default:
538                 qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
539                         arg(subTypeValue.typeName(), detail.definitionName(), field.name()));
540                 continue;
541             }
542
543             predicates.append(field.propertyChain().last().iri());
544
545             foreach(const ClassInfoBase &i, field.subTypeClasses()) {
546                 if (subTypes.contains(i.text())) {
547                     addEntity(classes, entities, predicates, i.iri());
548                 }
549             }
550         }
551     }
552
553     return entities;
554 }
555
556 static QVariant
557 findSubTypeValue(const DetailMapping &detail, const QTrackerContactDetailField *const subTypeField)
558 {
559     QVariant value;
560
561     if (0 != subTypeField && subTypeField->hasSubTypeProperties()) {
562         value = detail.fieldValue(subTypeField->name());
563
564         if (QVariant::String != value.type()) {
565             qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
566                     arg(value.typeName(), detail.definitionName(), subTypeField->name()));
567             value = QVariant();
568         }
569     }
570
571     return value;
572
573 }
574
575 static const QUrl &
576 findSubTypePredicate(const DetailMapping &detail, const QTrackerContactDetailField &field,
577                      const QTrackerContactDetailField *const subTypeField)
578 {
579     const QVariant subTypeValue = findSubTypeValue(detail, subTypeField);
580
581     if (subTypeValue.isValid()) {
582         foreach(const PropertyInfoBase &pi, subTypeField->subTypeProperties()) {
583             if (pi.value() == subTypeValue) {
584                 return pi.iri();
585             }
586         }
587     }
588
589     return field.propertyChain().last().iri();
590 }
591
592 QString
593 UpdateBuilder::makeUniqueName(const QString &basename)
594 {
595     return basename + QString::number(m_variableCounter++);
596 }
597
598 RDFVariable &
599 UpdateBuilder::lookupAffiliation(const QString &context)
600 {
601     AffiliationMap::Iterator affilination = m_affiliations.find(context);
602
603     // create nco::Affiliation entity when needed
604     if (affilination == m_affiliations.end()) {
605         const QString baseName = QTrackerContactQueryBuilder::name(QLatin1String("Affiliation"), context);
606         affilination = m_affiliations.insert(context, RDFVariable(makeUniqueName(baseName)));
607    }
608
609     return *affilination;
610 }
611
612 void
613 UpdateBuilder::insertCustomValues(const RDFVariable &subject,
614                                   const QString &detailName, const QString &fieldName,
615                                   const QVariant &value, const QVariantList &allowableValues)
616 {
617     QVariantList customValues;
618
619     switch(value.type()) {
620     case QVariant::StringList:
621         foreach(const QString &element, value.toStringList()) {
622             if (not element.isEmpty() && not allowableValues.contains(element)) {
623                 customValues.append(element);
624             }
625         }
626
627         break;
628
629     case QVariant::List:
630         foreach(const QVariant &element, value.toList()) {
631             if (not element.isNull() && not allowableValues.contains(element)) {
632                 customValues.append(element);
633             }
634         }
635
636         break;
637
638     default:
639         if (not value.isNull() && not allowableValues.contains(value)) {
640             customValues.append(value);
641         }
642     }
643
644     foreach(const QVariant &element, customValues) {
645         RDFVariable property(makeUniqueName(QTrackerContactQueryBuilder::name(detailName, fieldName)));
646
647         insert(subject, rdf::type::iri(), nie::InformationElement::iri());
648         insert(subject, nao::hasProperty::iri(), property);
649
650         insert(property, rdf::type::iri(), nao::Property::iri());
651         insert(property, nao::propertyName::iri(), LiteralValue(fieldName));
652         insert(property, nao::propertyValue::iri(), QTrackerContactQueryBuilder::value(element));
653     }
654 }
655
656 void
657 UpdateBuilder::insertCustomValues(const RDFVariable &subject, const QString &detailName,
658                                   const QTrackerContactDetailField &field,
659                                   const QVariant &value)
660 {
661     insertCustomValues(subject, detailName, field.name(), value,
662                        field.describeAllowableValues());
663 }
664
665 static PropertyInfoList::ConstIterator
666 endOfPropertyChain(const QTrackerContactDetailField &field)
667 {
668     if (field.isWithoutMapping()) {
669         return field.propertyChain().end();
670     }
671
672     return field.propertyChain().end() - 1;
673 }
674
675 void
676 UpdateBuilder::insertDetailField(QContactLocalId contactId,
677                                  const RDFVariable &subject, const DetailMapping &detail,
678                                  const QTrackerContactDetailField &field, const QVariant &value,
679                                  const EntityHash &entities, PredicateVariableHash &objectCache)
680 {
681     if (not value.isValid()) {
682         qctWarn(QString::fromLatin1("Value must be valid for %2 field of %1 detail").
683                 arg(detail.definitionName(), field.name()));
684         return;
685     }
686
687     const QTrackerContactDetailField *const subTypeField = detail.subTypeField();
688     const PropertyInfoList &propertyChain = field.propertyChain();
689
690     if (propertyChain.isEmpty()) {
691         qctWarn(QString::fromLatin1("RDF property chain needed for %2 field of %1 detail").
692                 arg(detail.definitionName(), field.name()));
693         return;
694     }
695
696     if (propertyChain.first().readOnly()) {
697         return;
698     }
699
700     PredicateChain predicateChain;
701     RDFVariableList axis;
702     axis.append(subject);
703
704     const PropertyInfoList::ConstIterator end = endOfPropertyChain(field);
705     const PropertyInfoList::ConstIterator tail = propertyChain.end() - 1;
706
707     for(PropertyInfoList::ConstIterator pi = propertyChain.begin(); pi != end; ++pi) {
708         if (pi->readOnly()) {
709             break;
710         }
711
712         PredicateVariableHash::ConstIterator object = objectCache.find(predicateChain << pi->iri());
713
714         if (object == objectCache.end()) {
715             // figure out subject IRI for this field
716             QUrl subjectIri;
717
718             const PropertyInfoList::ConstIterator npi = pi + 1;
719
720             if (npi != propertyChain.end()) {
721                 const QTrackerContactSubject::Scheme subjectScheme = npi->subjectScheme();
722
723                 if (QTrackerContactSubject::isContentScheme(subjectScheme)) {
724                     // Prefer the detail's explicit URI for tail nodes, but only if the
725                     // property's subject scheme matches the detail's scheme. This additional
726                     // check is needed because details like Organization randomly spread their
727                     // fields over many different entities.
728                     if (npi == tail && subjectScheme == detail.detailScheme()) {
729                         subjectIri = detail.detailUri();
730                     }
731
732                     // If we don't have an entity IRI yet generate it from property and value.
733                     if (subjectIri.isEmpty()) {
734                         subjectIri = npi->makeSubject(contactId, value);
735                     }
736                 }
737             }
738
739             // name object variable and assign subject IRI
740             RDFVariable objectVariable;
741
742             if (subjectIri.isEmpty()) {
743                 // create named blank variable if no subject IRI could be build
744                 const QString name = makeUniqueName(detail.definitionName());
745                 objectVariable.metaAssign(RDFVariable(name));
746             } else {
747                 // assign generated subject IRI
748                 objectVariable.metaAssign(subjectIri);
749             }
750
751             // assign RDF classes to this field
752             EntityHash::ConstIterator entity = entities.find(predicateChain);
753
754             if (entities.end() == entity) {
755                 qctWarn(QString::fromLatin1("Cannot find entities for %3 property of %1 detail's %2 field").
756                         arg(detail.definitionName(), field.name(), predicateChain.last().toString()));
757             }
758
759             for(; entity != entities.end() && entity.key() == predicateChain; ++entity) {
760                 // create insert statement of this entity type
761                 object = objectCache.insert(predicateChain, objectVariable);
762                 insert(*object, rdf::type::iri(), **entity);
763             }
764         }
765
766         // create insert statement with the field's value
767         if (pi->inverse()) {
768             insert(*object, pi->iri(), axis.last());
769         } else {
770             insert(axis.last(), pi->iri(), *object);
771         }
772
773         axis.append(*object);
774
775         // insert custom values for subTypes field
776         if (subTypeField && subTypeField->permitsCustomValues()) {
777             insertCustomValues(axis.last(), detail.definitionName(), *subTypeField,
778                                detail.fieldValue(subTypeField->name()));
779         }
780     }
781
782     // find proper type of the value to insert
783     if (not field.isWithoutMapping()) {
784         const QUrl subTypePredicate = findSubTypePredicate(detail, field, subTypeField);
785
786         if (field.propertyChain().last().inverse()) {
787             insert(QTrackerContactQueryBuilder::value(value), subTypePredicate, axis.last());
788         } else {
789             insert(axis.last(), subTypePredicate, QTrackerContactQueryBuilder::value(value));
790         }
791     }
792
793     // insert computed values
794     foreach(const PropertyInfoBase &pi, field.computedProperties()) {
795         QVariant computedValue;
796
797         if (pi.conversion()->makeValue(value, computedValue)) {
798             insert(axis.last(), pi.iri(), QTrackerContactQueryBuilder::value(computedValue));
799         }
800     }
801
802     // store custom values
803     if (field.permitsCustomValues()) {
804         insertCustomValues(axis.last(), detail.definitionName(), field, value);
805     }
806 }
807
808 UpdateBuilder::UpdateBuilder(const QTrackerContactSaveRequest *request, const QContact &contact)
809     : m_logger(request->qctLogger())
810     , m_schema(request->engine()->schema(contact.type()))
811     , m_classes(request->engine()->classes())
812     , m_graphIri(request->engine()->graphIri())
813     , m_variableCounter(1)
814 {
815     // figure out the contact's timestamps
816    QContactTimestamp timestampDetail(contact.detail<QContactTimestamp>());
817
818    m_accessed = timestampDetail.variantValue(QContactTimestamp__FieldAccessedTimestamp).toDateTime();
819    m_created = timestampDetail.created();
820    m_lastModified = timestampDetail.lastModified();
821
822    if (m_accessed.isNull()) {
823        m_accessed = request->timestamp();
824    }
825
826    if (m_created.isNull()) {
827        m_created = request->timestamp();
828        m_hadCreated = false;
829    } else {
830        m_hadCreated = true;
831    }
832
833    if (m_lastModified.isNull() || 0 != contact.localId()) {
834        // only preserve the lastModified field for new contacts...
835        m_lastModified = request->timestamp();
836    }
837
838    // figure out the contact's GUID
839    QContactGuid guidDetail(contact.detail<QContactGuid>());
840
841    m_guid = guidDetail.guid();
842    m_hadGuid = not m_guid.isEmpty();
843
844    if (m_guid.isEmpty()) {
845        m_guid = qctUuidString();
846    }
847
848    // calculate local contact ID
849    m_contactLocalId = contact.localId();
850
851    if (0 == m_contactLocalId) {
852        m_contactLocalId = qHash(guid());
853    }
854
855    m_isSelfContact = (m_contactLocalId == request->engine()->selfContactId(0));
856    m_contactIri = schema().makeContactIri(contactLocalId()).toString();
857
858    // collect details and update their URIs
859    foreach(const QContactDetail &detail, contact.details()) {
860         const QTrackerContactDetail *definition(m_schema.detail(detail.definitionName()));
861
862         if (0 == definition) {
863             if (m_schema.isSyntheticDetail(detail.definitionName())) {
864                 continue; // don't even store synthesized details as nao:Property
865             }
866         }
867
868         m_mappings.append(DetailMapping(detail, definition));
869     }
870
871    foreach(DetailMapping detail, m_mappings) {
872        detail.updateDetailUri(contactLocalId(), m_mappings);
873    }
874 }
875
876 void
877 UpdateBuilder::insert(const RDFVariable &subject, const RDFVariable &predicate,
878                       const RDFVariable &value, bool explicitValue)
879 {
880     if (explicitValue) {
881         m_explicitInsertStatements += RDFStatement(subject, predicate, value);
882     } else {
883         RDFVariable restricted = subject.metaValue().iri();
884         RDFVariable property = restricted.optional().property(predicate);
885
886         m_implicitInsertStatements += RDFStatement(restricted, predicate, value);
887
888         not restricted.variable(property).isBound();
889     }
890 }
891
892 void
893 UpdateBuilder::insertCustomDetail(RDFVariable &subject, const DetailMapping &detail)
894 {
895     const QString detailName = detail.definitionName();
896     const QVariantMap fields = detail.fieldValues();
897
898     RDFVariable detailProperty(makeUniqueName(detailName));
899
900     insert(subject, nao::hasProperty::iri(), detailProperty);
901     insert(detailProperty, rdf::type::iri(), nao::Property::iri());
902     insert(detailProperty, nao::propertyName::iri(), LiteralValue(detailName));
903
904     for(QVariantMap::ConstIterator i = fields.begin(); i != fields.end(); ++i) {
905         insertCustomValues(detailProperty, detailName, i.key(), i.value());
906     }
907 }
908
909 void
910 UpdateBuilder::restrictAffiliation(const RDFVariable &contact,
911                                    const RDFVariable &affiliation,
912                                    const QString &context)
913 {
914     insert(affiliation, rdf::type::iri(), nco::Affiliation::iri());
915     insert(affiliation, rdfs::label::iri(), LiteralValue(context));
916     insert(contact, nco::hasAffiliation::iri(), affiliation);
917 }
918
919 void
920 UpdateBuilder::collectInsertions()
921 {
922     m_explicitInsertStatements.clear();
923     m_implicitInsertStatements.clear();
924
925     // Removing Role properties also removed the contact's PersonContact type,
926     // so we have to restore it.
927     RDFVariable contact = schema().makeContactIri(contactLocalId());
928
929     // Restore the resource type, we removed it for quick propertly deletion.
930     foreach(const QUrl &classIri, schema().contactClassIris()) {
931         insert(contact, rdf::type::iri(), classIri);
932     }
933
934     // Always update the local UID to support a-priori contacts like the self contact.
935     // Accept update errors if that property already exists with different value as
936     // this property is assumed to be immutable.
937     const LiteralValue contactLocalUID(QString::number(contactLocalId()));
938     insert(contact, nco::contactLocalUID::iri(), contactLocalUID);
939
940     // Prepare affiliation for work context in case the contact has an organization detail.
941     // This one should be stored with the same affiliation as the work context.
942     RDFVariable affiliation = lookupAffiliation(QContactDetail::ContextWork);
943
944     // Collect inserts for each regular detail.
945     foreach(const DetailMapping &detail, mappings()) {
946         // Store custom details via nao:Property.
947         if (detail.isCustomDetail()) {
948             insertCustomDetail(contact, detail);
949             continue;
950         }
951
952         // Don't save synthesized details.
953         if (detail.isSyntheticDetail()) {
954             continue;
955         }
956
957         const QVariantMap detailValues = detail.fieldValues();
958         const EntityHash entities = collectEntities(classes(), detail);
959         QStringList detailContexts = detail.contexts();
960         PredicateVariableHash objectCache;
961
962         const bool hasOtherContext =
963                 (detailContexts.isEmpty() ||
964                  detailContexts.removeAll(QContactDetail::ContextOther) > 0);
965
966         // Add the prepared work context's affiliation to object cache
967         // to make sure it will be used when an organization detail would be used.
968         objectCache.insert(PredicateChain() << nco::hasAffiliation::iri(), affiliation);
969
970         foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
971             if (field.hasSubTypes()) {
972                 // Subtypes are stored indirectly by assigning the proper resource class
973                 // to the RDF resource representing this field, or by selecting the matching
974                 // RDF property for storing the detail value.
975                 continue;
976             }
977
978             const QVariantMap::ConstIterator fieldValue = detailValues.find(field.name());
979
980             if (fieldValue == detailValues.end()) {
981                 // Could not find value for this field.
982                 continue;
983             }
984
985             QVariant rdfValue;
986
987             if (not field.makeValue(*fieldValue, rdfValue)) {
988                 // Could not compute the RDF value for this field.
989                 continue;
990             }
991
992             if (hasOtherContext) {
993                 insertDetailField(contactLocalId(), contact, detail, field,
994                                   rdfValue, entities, objectCache);
995             }
996
997             foreach(const QString &context, detailContexts) {
998                 RDFVariable contextAffiliation = lookupAffiliation(context);
999                 restrictAffiliation(contact, contextAffiliation, context);
1000
1001                 insertDetailField(contactLocalId(), contextAffiliation,
1002                                   detail, field, rdfValue, entities, objectCache);
1003             }
1004         }
1005     }
1006
1007     // Check if the prepared affiliation was used for binding contact details.
1008     // In that case this prepare affiliation must be turned into a proper one.
1009     foreach(const RDFStatement &statement, m_explicitInsertStatements) {
1010         if (statement.subject().metaIdentifier() == affiliation.metaIdentifier()) {
1011             restrictAffiliation(contact, statement.subject(), QContactDetail::ContextWork);
1012             break;
1013         }
1014     }
1015
1016     // Insert timestamps and such.
1017     insert(contact, nie::contentLastModified::iri(), LiteralValue(lastModified()));
1018     insert(contact, nie::contentCreated::iri(), LiteralValue(created()), hadCreated());
1019     insert(contact, nco::contactUID::iri(), LiteralValue(guid()), hadGuid());
1020
1021     // Add prepared INSERT statements to the query string.
1022     RDFUpdate update;
1023
1024     update.addInsertion(m_explicitInsertStatements, m_graphIri);
1025
1026     foreach(const RDFStatement &statement, m_implicitInsertStatements) {
1027         update.addInsertion(statement, m_graphIri);
1028     }
1029
1030     m_queryString += BackEnds::Tracker::tracker()->rawQueryString(update);
1031 }
1032
1033 void
1034 UpdateBuilder::collectRelatedObjects()
1035 {
1036     m_implictlyRelatedObjects.clear();
1037     m_explicitlyRelatedObjects.clear();
1038
1039     foreach(const QTrackerContactDetail &detail, schema().details()) {
1040         const QTrackerContactDetailField *const subjectField = detail.subjectField();
1041
1042         if (0 != subjectField) {
1043             const PropertyInfoList &subjectPropertyChain = subjectField->propertyChain();
1044             const PropertyInfoBase &subjectProperty = subjectPropertyChain.last();
1045
1046             if (subjectPropertyChain.first().readOnly()) {
1047                 continue;
1048             }
1049
1050             if (QTrackerContactSubject::isContentScheme(subjectProperty.subjectScheme())) {
1051                 // delete related objects by their content IRI
1052                 foreach(const DetailMapping &mapping, mappings()) {
1053                     if (mapping.definitionName() != detail.name()) {
1054                         continue;
1055                     }
1056
1057                     const QUrl objectIri = mapping.makeSubjectIri(contactLocalId());
1058
1059                     if (objectIri.isEmpty()) {
1060                         qctWarn(QString::fromLatin1("Empty object IRI for %1 detail which "
1061                                                     "has a content IRI").arg(detail.name()));
1062                         continue;
1063                     }
1064
1065                     const QString typeIri =
1066                             (subjectProperty.inverse() ? subjectProperty.rangeIri()
1067                                                        : subjectProperty.domainIri()).toString();
1068
1069                     m_explicitlyRelatedObjects[typeIri] += ExplictObjectInfo(objectIri, detail);
1070                 }
1071
1072                 continue; // next detail
1073             }
1074         }
1075
1076         // no content IRI known, delete via association
1077         if (not detail.predicateChains().isEmpty()) {
1078             m_implictlyRelatedObjects += detail;
1079         }
1080     }
1081 }
1082
1083 void
1084 UpdateBuilder::appendPredicateChain(const QString &prefix, const PredicateChain &chain,
1085                                     const QString &suffix, const QString &indent)
1086 {
1087     if (not chain.isEmpty()) {
1088         PredicateChain::ConstIterator pi = chain.begin();
1089
1090         m_queryString += prefix;
1091         m_queryString += pi->toString();
1092
1093         while(++pi != chain.end()) {
1094             static const QString chainPattern = QLatin1String("> ?%1 .\n%2?%1 <%3");
1095             m_queryString += chainPattern.arg(makeUniqueName(), indent, pi->toString());
1096         }
1097
1098         m_queryString += suffix;
1099
1100         return;
1101     }
1102 }
1103
1104 void
1105 UpdateBuilder::deleteCustomProperties()
1106 {
1107     // SPARQL fragments used for building the query.
1108     static const QString queryPrefix = QLatin1String
1109             ("\n"
1110 #ifndef QT_NO_DEBUG
1111              "#----------------------------------------------------------------------.\n"
1112              "# Delete custom detail fields of associated objects and custom details |\n"
1113              "#----------------------------------------------------------------------'\n"
1114 #endif // QT_NO_DEBUG
1115              "\n"
1116              "DELETE\n"
1117              "{\n"
1118              "  GRAPH <%1>\n"
1119              "  {\n"
1120              "    ?property a ?propertyType .\n"
1121              "  }\n"
1122              "}\n"
1123              "WHERE\n"
1124              "{\n"
1125              "  GRAPH <%1>\n"
1126              "  {\n");
1127     static const QString implicitResourcePrefix = QLatin1String
1128             ("    {\n"
1129              "      <%1> <");
1130     static const QString affiliatedResourcePrefix = QLatin1String
1131             ("    {\n"
1132              "      <%1> nco:hasAffiliation ?affiliation .\n"
1133              "      ?affiliation <");
1134     static const QString implicitResourceIndent = QLatin1String
1135             ("      ");
1136     static const QString implicitResourceSuffix = QLatin1String
1137             ("> ?resource .\n"
1138              "    }\n"
1139              "    UNION\n");
1140     static const QString querySuffix = QLatin1String
1141             ("    {\n"
1142              "      <%1> ?predicate ?resource .\n"
1143              "      FILTER(?predicate IN (<%2>)) .\n"
1144              "    }\n"
1145              "\n"
1146              "    UNION\n"
1147              "    {\n"
1148              "      FILTER(?resource IN (<%3>)) .\n"
1149              "    }\n"
1150              "\n"
1151              "    ?resource nao:hasProperty ?property .\n"
1152              "    ?property a ?propertyType .\n"
1153              "  }\n"
1154              "}\n");
1155
1156     // Glue together the SPARQL fragments.
1157     m_queryString += queryPrefix.arg(m_graphIri.toString());
1158
1159     QStringList shortChains(nao::hasProperty::iri().toString());
1160
1161     foreach(const QTrackerContactDetail &detail, m_implictlyRelatedObjects) {
1162         foreach(const PredicateChain &chain, detail.customValueChains()) {
1163             if (detail.hasContext()) {
1164                 appendPredicateChain(affiliatedResourcePrefix.arg(m_contactIri), chain,
1165                                      implicitResourceSuffix, implicitResourceIndent);
1166             }
1167
1168             if (chain.length() > 1) {
1169                 appendPredicateChain(implicitResourcePrefix.arg(m_contactIri), chain,
1170                                      implicitResourceSuffix, implicitResourceIndent);
1171             } else {
1172                 shortChains += chain.first().toString();
1173             }
1174         }
1175     }
1176
1177     QStringList objectIris(m_contactIri);
1178
1179     foreach(const ExplictObjectInfoList &objects, m_explicitlyRelatedObjects.values()) {
1180         foreach(const ExplictObjectInfo &info, objects) {
1181             if (info.hasCustomFields()) {
1182                 objectIris += info.objectIri();
1183             }
1184         }
1185     }
1186
1187     m_queryString += querySuffix.arg(m_contactIri, shortChains.join(QLatin1String(">, <")),
1188                                      objectIris.join(QLatin1String(">, <")));
1189 }
1190
1191 void
1192 UpdateBuilder::deleteRelatedObjects()
1193 {
1194     // SPARQL fragments used for building the query.
1195     static const QString implicitQueryPrefix = QLatin1String
1196             ("\n"
1197 #ifndef QT_NO_DEBUG
1198              "#----------------------------------------------------.\n"
1199              "# Delete associated objects via their property chain |\n"
1200              "#----------------------------------------------------'\n"
1201 #endif // QT_NO_DEBUG
1202              "\n"
1203              "DELETE\n"
1204              "{\n"
1205              "  ?resource a ?resourceType .\n"
1206              "}\n"
1207              "WHERE\n"
1208              "{\n");
1209     static const QString implicitResourcePrefix = QLatin1String
1210             ("  {\n"
1211              "    <%1> <");
1212     static const QString affiliatedResourcePrefix = QLatin1String
1213             ("  {\n"
1214              "    <%1> nco:hasAffiliation ?affiliation .\n"
1215              "    ?affiliation <");
1216     static const QString implicitResourceIndent = QLatin1String
1217             ("    ");
1218     static const QString implicitResourceSuffix = QLatin1String
1219             ("> ?resource .\n"
1220              "  }\n"
1221              "  UNION\n");
1222     static const QString implicitQuerySuffix = QLatin1String
1223             ("  {\n"
1224              "    <%1> ?predicate ?resource .\n"
1225              "    FILTER(?predicate IN (<%2>)) ."
1226              "  }\n"
1227              "\n"
1228              "  ?resource a ?resourceType .\n"
1229              "}\n"
1230              "\n"
1231 #ifndef QT_NO_DEBUG
1232              "#------------------------------------------------------------.\n"
1233              "# Delete associated objects via their well known content IRI |\n"
1234              "#------------------------------------------------------------'\n");
1235 #endif // QT_NO_DEBUG
1236     static const QString explictObjectQuery = QLatin1String
1237             ("\n"
1238              "DELETE\n"
1239              "{\n"
1240              "  ?resource a <%1> .\n"
1241              "}\n"
1242              "WHERE\n"
1243              "{\n"
1244              "  ?resource a <%1> .\n"
1245              "  FILTER(?resource IN (<%2>)) .\n"
1246              "}\n");
1247
1248     // Glue together the SPARQL fragments.
1249     m_queryString += implicitQueryPrefix;
1250
1251     QStringList shortChains(nao::hasProperty::iri().toString());
1252
1253     foreach(const QTrackerContactDetail &detail, m_implictlyRelatedObjects) {
1254         foreach(const PredicateChain &chain, detail.predicateChains()) {
1255             if (detail.hasContext()) {
1256                 appendPredicateChain(affiliatedResourcePrefix.arg(m_contactIri), chain,
1257                                      implicitResourceSuffix, implicitResourceIndent);
1258             }
1259
1260             if (chain.length() > 1) {
1261                 appendPredicateChain(implicitResourcePrefix.arg(m_contactIri), chain,
1262                                      implicitResourceSuffix, implicitResourceIndent);
1263             } else {
1264                 shortChains += chain.first().toString();
1265             }
1266         }
1267     }
1268
1269     m_queryString += implicitQuerySuffix.arg(m_contactIri, shortChains.join(QLatin1String(">, <")));
1270
1271     for(ExplictObjectInfoListMap::ConstIterator
1272         i = m_explicitlyRelatedObjects.begin(); i != m_explicitlyRelatedObjects.end(); ++i) {
1273         QStringList objectIris;
1274
1275         foreach(const ExplictObjectInfo &info, i.value()) {
1276             objectIris += info.objectIri();
1277         }
1278
1279         if (not objectIris.isEmpty()) {
1280             m_queryString += explictObjectQuery.arg(i.key(), objectIris.join(QLatin1String(">, <")));
1281         }
1282     }
1283 }
1284
1285 void
1286 UpdateBuilder::deleteContactProperties()
1287 {
1288     // SPARQL fragments used for building the query.
1289     static const QString queryPrefix = QLatin1String
1290             ("\n"
1291 #ifndef QT_NO_DEBUG
1292              "#---------------------------------------------------------.\n"
1293              "# Delete the contact properties within the plugin's graph |\n"
1294              "#---------------------------------------------------------'\n"
1295 #endif // QT_NO_DEBUG
1296              "\n"
1297              "DELETE\n"
1298              "{\n"
1299              "  GRAPH <%2>\n"
1300              "  {\n"
1301              "    <%1> ?predicate ?object .\n"
1302              "  }\n"
1303              "}\n"
1304              "WHERE\n"
1305              "{\n"
1306              "  GRAPH <%2>\n"
1307              "  {\n"
1308              "    <%1> ?predicate ?object .\n"
1309              "    FILTER(?predicate NOT IN (nco:contactLocalUID");
1310     static const QString guidFilter = QLatin1String(",nco:contactUID");
1311     static const QString createdFilter = QLatin1String(",nie:contentCreated");
1312     static const QString querySuffix = QLatin1String(",rdf:type)) .\n"
1313              "  }\n"
1314              "}\n");
1315
1316     // Glue together the SPARQL fragments.
1317     m_queryString += queryPrefix.arg(m_contactIri, m_graphIri.toString());
1318
1319     if (not hadGuid()) {
1320         // Preserve possibly existing GUID if the caller didn't provide one.
1321         m_queryString += guidFilter;
1322     }
1323
1324     if (not hadCreated()) {
1325         // Preserve possibly existing creation timestamp  if the caller didn't provide one.
1326         m_queryString += createdFilter;
1327     }
1328
1329     m_queryString += querySuffix;
1330 }
1331
1332 QString
1333 UpdateBuilder::queryString()
1334 {
1335     m_queryString.clear();
1336
1337     // collect DELETE statements
1338     collectRelatedObjects();
1339     deleteCustomProperties();
1340     deleteRelatedObjects();
1341     deleteContactProperties();
1342
1343     // collect INSERT statements
1344     collectInsertions();
1345
1346     return m_queryString;
1347 }
1348
1349 void
1350 QTrackerContactSaveRequest::normalizeCurrentContact()
1351 {
1352     QContact &contact = m_contacts[m_contactOffset];
1353
1354     foreach(const QTrackerContactDetail &detail, engine()->schema(contact.type()).details()) {
1355         if (not detail.isUnique()) {
1356             continue;
1357         }
1358
1359         QList<QContactDetail> contactDetails(contact.details(detail.name()));
1360
1361         if (contactDetails.count() < 2) {
1362             continue;
1363         }
1364
1365         qctWarn(QString::fromLatin1("Dropping odd details for contact %1: "
1366                                     "%2 detail must be unique").
1367                 arg(m_contactOffset).arg(detail.name()));
1368
1369         for(int i = 1; i < contactDetails.count(); ++i) {
1370             contact.removeDetail(&contactDetails[i]);
1371         }
1372     }
1373 }
1374
1375 static QString
1376 makeAvatarCacheFileName(const QImage& image)
1377 {
1378     // FIXME: H-Contacts needs to fix loading of images from URI
1379     // when not inside their own cache directory.
1380
1381     const QDir avatarCacheDir = qctAvatarCacheDir();
1382
1383     // Create avatar cache directory when needed.
1384
1385     if (not avatarCacheDir.mkpath(QLatin1String("."))) {
1386         qctWarn(QString::fromLatin1("Cannot create avatar cache folder %1: %2").
1387                 arg(avatarCacheDir.path(), strerror(errno)));
1388         return QString();
1389     }
1390
1391     // Create filename from cryptographic hash of the pixel data. Probability that two
1392     // different pictures on the same device have the same hash code, is significantly
1393     // smaller than probability that we mess up because of miscalculations caused by
1394     // electrostatics and flipping bits. That's why we can apply this cheap trick.
1395
1396     const QByteArray pixels(reinterpret_cast<const char *>(image.bits()), image.byteCount());
1397     const QString pixelHash = QCryptographicHash::hash(pixels, QCryptographicHash::Sha1).toHex();
1398
1399     const QString fileName = QString::fromLatin1("%1.png").arg(pixelHash);
1400     return avatarCacheDir.absoluteFilePath(fileName);
1401 }
1402
1403 static QSet<QString>
1404 findAccountDetailUris(const QContact &contact)
1405 {
1406     QSet<QString> onlineAccountUris;
1407
1408     foreach(const QContactOnlineAccount &detail, contact.details<QContactOnlineAccount>()) {
1409         onlineAccountUris += detail.detailUri();
1410     }
1411
1412     return onlineAccountUris;
1413 }
1414
1415 static bool
1416 isLinkedDetail(const QContactDetail &detail, const QSet<QString> &linkedDetailUris)
1417 {
1418     foreach(const QString &uri, detail.linkedDetailUris()) {
1419         if (linkedDetailUris.contains(uri)) {
1420             return true;
1421         }
1422     }
1423
1424     return false;
1425 }
1426
1427 bool
1428 QTrackerContactSaveRequest::normalizeAvatars()
1429 {
1430     QContact &contact = m_contacts[m_contactOffset];
1431     const QSet<QString> accountDetailUris = findAccountDetailUris(contact);
1432
1433     // Find user supplied thumbnail of the contact itself.
1434     QContactThumbnail defaultThumbnail;
1435
1436     foreach(const QContactThumbnail &thumbnail, contact.details<QContactThumbnail>()) {
1437         if (not isLinkedDetail(thumbnail, accountDetailUris)) {
1438             defaultThumbnail = thumbnail;
1439             break;
1440         }
1441     }
1442
1443     if (defaultThumbnail.isEmpty()) {
1444         return true;
1445     }
1446
1447     // Find default avatar of the contact itself.
1448     QContactAvatar defaultAvatar;
1449
1450     foreach(const QContactAvatar &avatar, contact.details<QContactAvatar>()) {
1451         if (not isLinkedDetail(avatar, accountDetailUris)) {
1452             defaultAvatar = avatar;
1453             break;
1454         }
1455     }
1456
1457     // Get avatar thumbnail from detail and scale down when needed.
1458     const QSize avatarSize = QTrackerContactSettings().avatarSize();
1459     QImage thumbnailImage = defaultThumbnail.thumbnail();
1460
1461     if (thumbnailImage.width() > avatarSize.width() ||
1462         thumbnailImage.height() > avatarSize.height()) {
1463         thumbnailImage = thumbnailImage.scaled(avatarSize, Qt::KeepAspectRatio);
1464     }
1465
1466     // Save the avatar image when neeed. The cached file name is based on
1467     // a cryptographic hash of the thumbnail's pixels. So if there already
1468     // exists a image file with the calculated name, there is an incredibly
1469     // high probability the existing file is equal to this current avatar.
1470     // We prefer to not write the file in this case for better performance
1471     // and for better lifetime of the storage medium.
1472     const QString avatarCacheFileName = makeAvatarCacheFileName(thumbnailImage);
1473     bool avatarWritten = true;
1474
1475     if (avatarCacheFileName.isEmpty()) {
1476         avatarWritten = false; // failed to build file name
1477     } else if (not QFile::exists(avatarCacheFileName)) {
1478         QImageWriter writer(avatarCacheFileName);
1479
1480         avatarWritten = writer.write(thumbnailImage);
1481
1482         if (not avatarWritten) {
1483             qctWarn(QString::fromLatin1("Cannot save avatar thumbnail for contact %1: %2").
1484                     arg(m_contactOffset).arg(writer.errorString()));
1485         }
1486     }
1487
1488     if (avatarWritten) {
1489         // everything went find - update avatar detail and proceed
1490         defaultAvatar.setImageUrl(QUrl::fromLocalFile(avatarCacheFileName));
1491         contact.saveDetail(&defaultAvatar);
1492     } else if (not defaultAvatar.isEmpty()) {
1493         // failed to save avatar picture - remove avatar detail
1494         contact.removeDetail(&defaultAvatar);
1495     }
1496
1497     return avatarWritten;
1498 }
1499
1500 QString
1501 QTrackerContactSaveRequest::buildQuery(QContact &contact)
1502 {
1503     UpdateBuilder builder(this, contact);
1504
1505     // Create local contact id when needed
1506     QContactId id = contact.id();
1507     id.setLocalId(builder.contactLocalId());
1508     id.setManagerUri(engine()->managerUri());
1509     contact.setId(id);
1510
1511     // Build the update query
1512     return builder.queryString();
1513 }
1514
1515 ///////////////////////////////////////////////////////////////////////////////////////////////////
1516
1517 void
1518 QTrackerContactSaveRequest::saveContact()
1519 {
1520     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1521         qDebug()
1522                 << metaObject()->className() << m_stopWatch.elapsed()
1523                 << ": contact" << m_contactOffset << "- updating";
1524     }
1525
1526     // drop odd details
1527     normalizeCurrentContact();
1528
1529     // normalize and save avatar
1530     if (not normalizeAvatars()) {
1531         return;
1532     }
1533
1534     // build the update query and run it
1535     const QString queryString = buildQuery(m_contacts[m_contactOffset]);
1536
1537     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowUpdates)) {
1538         qDebug() << queryString;
1539     }
1540
1541     QDBusPendingCall updateCall = QctTracker::Resources::
1542             batchSparqlUpdate(m_trackerInstance, queryString,
1543                               engine()->trackerTimeout());
1544
1545     QTrackerContactSaveRequestTask *pendingUpdate =
1546             new QTrackerContactSaveRequestTask(updateCall, m_contactOffset, this);
1547
1548     connect(pendingUpdate, SIGNAL(failure(int,QString)), SLOT(failure(int,QString)));
1549     connect(pendingUpdate, SIGNAL(success(int)), SLOT(success(int)));
1550     m_pendingTasks.insert(m_contactOffset, pendingUpdate);
1551
1552     if (++m_batchSize >= engine()->batchSize()) {
1553         commit();
1554     }
1555 }
1556
1557 void
1558 QTrackerContactSaveRequest::proceed()
1559 {
1560     // Too many updates are running still, let's abort.
1561     // We'll get called again when next pending update is commited.
1562     if (m_pendingTasks.count() >= engine()->concurrencyLevel() / 2 &&
1563         m_pendingTasks.count() > 0) {
1564         return;
1565     }
1566
1567     while (m_contactOffset < m_contacts.count()) {
1568         // Now we have queued too many updates, let's abort.
1569         // We'll get called again when next pending update is commited.
1570         if (m_pendingTasks.count() >= engine()->concurrencyLevel()) {
1571             return;
1572         }
1573
1574         saveContact();
1575         m_contactOffset++;
1576     }
1577
1578     if (hasPendingUpdates()) {
1579         commit(); // We left the saveContact() loop, and now must commit the queued updates.
1580         return;
1581     }
1582
1583     // We left the saveContact() loop, and don't have any pending updates or
1584     // pending commits. This shows the save request was finished. Let's report this.
1585     if (not hasPendingCommit()) {
1586         if (m_errorMap.isEmpty()) {
1587             m_effectiveError = QContactManager::NoError;
1588         }
1589
1590         emitResult(m_effectiveError);
1591     }
1592 }
1593
1594 void
1595 QTrackerContactSaveRequest::commit()
1596 {
1597     // Don't do anything if another commit is running still.
1598     if (hasPendingCommit()) {
1599         return;
1600     }
1601
1602     // Dump some debug information when requested.
1603     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1604         qDebug()
1605                 << metaObject()->className() << m_stopWatch.elapsed()
1606                 << "- commiting" << m_batchSize << "pending updates";
1607     }
1608
1609     // Tell tracker to commit queued updates.
1610     QDBusPendingCall commitCall = QctTracker::Resources::
1611             batchCommit(m_trackerInstance, engine()->trackerTimeout());
1612
1613     m_commit.reset(new QDBusPendingCallWatcher(commitCall));
1614
1615     connect(m_commit.data(),
1616             SIGNAL(finished(QDBusPendingCallWatcher*)),
1617             SLOT(commitFinished()));
1618
1619     m_batchSize = 0;
1620 }
1621
1622 void
1623 QTrackerContactSaveRequest::commitFinished()
1624 {
1625     if (m_commit->isError()) {
1626         // Seems we don't have to do anything for for this error except for printing
1627         // it to console: We won't get a success signal for the failed update, and
1628         // therefore the failed contact should stay in m_errorMap[] causing proper
1629         // error reporting.
1630         qctWarn(QString::fromLatin1("Commit failed: %1").arg(m_commit->error().message()));
1631     }
1632
1633
1634     m_commit.reset(); // Mark this commit as done.
1635     proceed(); // Check if other updates are pending, or if more contacts must be written.
1636 }
1637
1638 void
1639 QTrackerContactSaveRequest::success(int offset)
1640 {
1641     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1642         qDebug()
1643                 << metaObject()->className() << m_stopWatch.elapsed()
1644                 << ": contact" << offset << " - update succeded";
1645     }
1646
1647     delete m_pendingTasks.take(offset); // Mark this update as done.
1648     m_errorMap.remove(offset); // Mark this update's contact as successfully written.
1649     proceed(); // Check if other updates are pending, or if more contacts must be written.
1650 }
1651
1652 void
1653 QTrackerContactSaveRequest::failure(int offset, QString const &message)
1654 {
1655     qctWarn(QString::fromLatin1("Failed to update contact %1: %2").arg(offset).arg(message));
1656
1657     delete m_pendingTasks.take(offset); // Mark this update as done.
1658     proceed(); // Check if other updates are pending, or if more contacts must be written.
1659
1660     // Notice: We don't remove the update's contact from m_errorMap[]
1661     // which results in proper error reporting.
1662 }
1663
1664 ///////////////////////////////////////////////////////////////////////////////////////////////////
1665
1666 bool
1667 QTrackerContactSaveRequest::hasPendingUpdates() const
1668 {
1669     return not m_pendingTasks.isEmpty();
1670 }
1671
1672 bool
1673 QTrackerContactSaveRequest::hasPendingCommit() const
1674 {
1675     return not m_commit.isNull();
1676 }
1677
1678 ///////////////////////////////////////////////////////////////////////////////////////////////////
1679
1680 bool
1681 QTrackerContactSaveRequest::start()
1682 {
1683     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1684         qDebug()
1685                 << metaObject()->className() << 0
1686                 << ": number of contacts:" << m_request->contacts().count();
1687     }
1688
1689     m_stopWatch.start();
1690
1691     // prepare error map with default values
1692     for(int i = 0; i < m_contacts.count(); ++i) {
1693         m_errorMap.insert(i, QContactManager::UnspecifiedError);
1694     }
1695
1696     // find tracker instance
1697     m_trackerInstance = QctTracker::instance();
1698
1699     if (m_trackerInstance.isEmpty()) {
1700         qctWarn(QString::fromLatin1("tracker service not found: %1").arg(QctTracker::lastError));
1701         return false;
1702     }
1703
1704     // start operation
1705     m_contactOffset = 0;
1706     QTimer::singleShot(0, this, SLOT(proceed()));
1707
1708     return true;
1709 }
1710
1711 void
1712 QTrackerContactSaveRequest::emitResult(QContactManager::Error error)
1713 {
1714     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowTiming)) {
1715         qDebug()
1716                 << metaObject()->className()  << m_stopWatch.elapsed()
1717                 << ": reporting result" << error;
1718     }
1719
1720     engine()->updateContactSaveRequest(m_request, m_contacts, error, m_errorMap,
1721                                       QContactAbstractRequest::FinishedState);
1722 }
1723
1724 ///////////////////////////////////////////////////////////////////////////////////////////////////
1725
1726 #include "contactsaverequest.moc"