Changes: Don't blindly trust the user-supplied guid to be a good filename
[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/connectionmanager.h>
55
56 #include <QtTracker/ontologies/nao.h>
57 #include <QtTracker/ontologies/nie.h>
58
59 ///////////////////////////////////////////////////////////////////////////////////////////////////
60
61 using namespace SopranoLive;
62 using namespace SopranoLive::Ontologies;
63
64 ///////////////////////////////////////////////////////////////////////////////////////////////////
65
66 template <typename T>
67 inline bool operator<(const QList<T> &a, const QList<T> &b)
68 {
69     for(int i = 0; i < a.count() && i < b.count(); ++i) {
70         if (a[i] < b[i]) {
71             return true;
72         } else if (b[i] < a[i]) {
73             return false;
74         }
75     }
76
77     return a.count() < b.count();
78 }
79
80 ///////////////////////////////////////////////////////////////////////////////////////////////////
81
82 typedef QList<struct DetailMapping> DetailMappingList;
83 typedef QMultiHash<PredicateChain, const QUrl *> EntityHash;
84
85 ///////////////////////////////////////////////////////////////////////////////////////////////////
86
87 struct DetailMapping
88 {
89     DetailMapping(QContactDetail *qtmDetail, const QTrackerContactDetail *rdfDetail)
90         : qtm(qtmDetail), rdf(rdfDetail)
91     {
92     }
93
94     /// a detail which has no own ontology representation
95     bool isCustomDetail() const;
96
97     /// is a detail which is generated from other details and not stored itself
98     bool isSyntheticDetail() const;
99
100     /// is a detail which belongs to an affiliation, see "Work" context
101     bool isAffiliatedDetail() const;
102
103     /// is a detail which is directly belongs to the person, see "Home" context
104     bool isPersonalDetail() const;
105
106     QSharedPointer<QContactDetail> qtm;
107     const QTrackerContactDetail *rdf;
108 };
109
110 inline bool
111 DetailMapping::isCustomDetail() const
112 {
113     return (0 == rdf);
114 }
115
116 inline bool
117 DetailMapping::isSyntheticDetail() const
118 {
119     return rdf->isSynthetic();
120 }
121
122 inline bool
123 DetailMapping::isAffiliatedDetail() const
124 {
125     return (rdf->hasContext() &&
126             qtm->contexts().contains(QContactDetail::ContextWork));
127 }
128
129 inline bool
130 DetailMapping::isPersonalDetail() const
131 {
132     if (not rdf->hasContext()) {
133         return true;
134     }
135
136     const QStringList contexts(qtm->contexts());
137     return (contexts.contains(QContactDetail::ContextHome) ||
138             not contexts.contains(QContactDetail::ContextWork));
139 }
140
141 ///////////////////////////////////////////////////////////////////////////////////////////////////
142
143 class UpdateBuilder
144 {
145 public:
146     UpdateBuilder(const QTrackerContactSaveRequest *request,
147                   const QContact &contact, const QDateTime &timestamp);
148
149     const QString & guid() const { return m_guid; }
150     bool hadGuid() const { return m_hadGuid; }
151
152     const QDateTime & accessed() const { return m_accessed; }
153     bool hadAccessed() const { return m_hadAccessed; }
154
155     const QDateTime & created() const { return m_created; }
156     bool hadCreated() const { return m_hadCreated; }
157
158     const QDateTime & lastModified() const { return m_lastModified; }
159     bool hadLastModified() const { return m_hadLastModified; }
160
161     const DetailMappingList & mappings() const { return m_mappings; }
162     const QContactLocalId & contactLocalId() const { return m_contactLocalId; }
163     const QTrackerContactDetailSchema & schema() const { return m_schema; }
164     const QTrackerClassHierarchy & classes() const { return m_classes; }
165
166     bool isSelfContact() const { return m_isSelfContact; }
167
168 public:
169     void collectInsertions(RDFUpdate &update) const;
170     void collectDeletions(RDFUpdate &update) const;
171
172 protected:
173     void deleteContactProperties(RDFUpdate &update) const;
174     void deleteAssociatedObjects(RDFUpdate &update) const;
175     void deleteCustomDetails(RDFUpdate &update) const;
176
177     const QctLogger & qctLogger() const { return m_request->qctLogger(); }
178
179 private:
180     const QTrackerContactSaveRequest *const m_request;
181
182     QString m_guid;
183     QDateTime m_accessed;
184     QDateTime m_created;
185     QDateTime m_lastModified;
186     DetailMappingList m_mappings;
187     QContactLocalId m_contactLocalId;
188     const QTrackerContactDetailSchema &m_schema;
189     const QTrackerClassHierarchy &m_classes;
190
191     bool m_hadGuid : 1;
192     bool m_hadAccessed : 1;
193     bool m_hadCreated : 1;
194     bool m_hadLastModified : 1;
195     bool m_isSelfContact : 1;
196 };
197
198 ///////////////////////////////////////////////////////////////////////////////////////////////////
199
200 struct Tracker
201 {
202     static const QString ServiceName;
203
204     struct Resources
205     {
206         static const QString Path;
207         static const QString Interface;
208         static const QString BatchSparqlUpdate;
209         static const QString BatchCommit;
210
211         static QDBusPendingCall batchSparqlUpdate(const QString &tracker,
212                                                   const QString &query,
213                                                   int timeout = INT_MAX)
214         {
215             QDBusMessage message = QDBusMessage::createMethodCall(tracker, Path, Interface,
216                                                                   BatchSparqlUpdate);
217
218             message.setArguments(QVariantList() << query);
219
220             return QTrackerDBusConnectionManager::sessionBus()->asyncCall(message, timeout);
221         }
222
223         static QDBusPendingCall batchCommit(const QString &tracker, int timeout = INT_MAX)
224         {
225             QDBusMessage message = QDBusMessage::createMethodCall(tracker, Path, Interface,
226                                                                   BatchCommit);
227             return QTrackerDBusConnectionManager::sessionBus()->asyncCall(message, timeout);
228         }
229     };
230 };
231
232 const QString Tracker::ServiceName(QLatin1String("org.freedesktop.Tracker1"));
233 const QString Tracker::Resources::Path(QLatin1String("/org/freedesktop/Tracker1/Resources"));
234 const QString Tracker::Resources::Interface(QLatin1String("org.freedesktop.Tracker1.Resources"));
235 const QString Tracker::Resources::BatchSparqlUpdate(QLatin1String("BatchSparqlUpdate"));
236 const QString Tracker::Resources::BatchCommit(QLatin1String("BatchCommit"));
237
238 ///////////////////////////////////////////////////////////////////////////////////////////////////
239
240 QTrackerContactUpdate::QTrackerContactUpdate(const QDBusPendingCall &call, int offset)
241     : m_call(call), m_offset(offset)
242 {
243     connect(&m_call, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(finished()));
244 }
245
246 QTrackerContactUpdate::~QTrackerContactUpdate()
247 {
248 }
249
250 void
251 QTrackerContactUpdate::finished()
252 {
253     if (m_call.isError()) {
254         emit failure(m_offset, m_call.error().message());
255     } else {
256         emit success(m_offset);
257     }
258 }
259
260 ///////////////////////////////////////////////////////////////////////////////////////////////////
261
262 QTrackerContactSaveRequest::QTrackerContactSaveRequest(QContactAbstractRequest *request,
263                                                        QContactTrackerEngine *engine,
264                                                        QObject *parent) :
265     QTrackerBaseRequest<QContactSaveRequest>(request, engine, parent),
266     m_timestamp(QDateTime::currentDateTime()),
267     m_effectiveError(QContactManager::UnspecifiedError),
268     m_contactOffset(0), m_batchSize(0)
269 {
270     m_contacts.append(m_request->contacts());
271 }
272
273 QTrackerContactSaveRequest::~QTrackerContactSaveRequest()
274 {
275 }
276
277 ///////////////////////////////////////////////////////////////////////////////////////////////////
278
279 static QString
280 makeContactUID()
281 {
282     const QString uidString(QUuid::createUuid().toString());
283     return uidString.mid(1, uidString.length() - 2);
284 }
285
286 ///////////////////////////////////////////////////////////////////////////////////////////////////
287
288 static void
289 addEntity(const QTrackerClassHierarchy &classes, EntityHash &entities,
290           const PredicateChain &predicates, const QUrl &newIri)
291 {
292     foreach(const QUrl *const oldIri, entities.values(predicates)) {
293         if (classes.isSubClassOf(*oldIri, newIri)) {
294             // skipping already covered entity type
295             return;
296         }
297
298         if (classes.isSubClassOf(newIri, *oldIri)) {
299             // removing too generic entity type which is covered by newIri
300             entities.remove(predicates, oldIri);
301         }
302     }
303
304     // adding the new entity type
305     entities.insertMulti(predicates, &newIri);
306 }
307
308 static EntityHash
309 collectEntities(const QTrackerClassHierarchy &classes, const DetailMapping &detail)
310 {
311     EntityHash entities;
312
313     foreach(const QTrackerContactDetailField &field, detail.rdf->fields()) {
314         if (field.propertyChain().isEmpty()) {
315             continue;
316         }
317
318         PredicateChain predicates;
319
320         const PropertyInfoList::ConstIterator end(field.propertyChain().end() - 1);
321         for(PropertyInfoList::ConstIterator i(field.propertyChain().begin()); i != end; ++i) {
322             const QUrl &predicateIri(i->iri());
323             const PropertyInfoBase &pi(*(i + 1));
324
325             if (pi.inverse()) {
326                 addEntity(classes, entities, predicates << predicateIri, pi.rangeIri());
327             } else {
328                 addEntity(classes, entities, predicates << predicateIri, pi.domainIri());
329             }
330         }
331
332         if (field.hasSubTypeClasses()) {
333             QVariant subTypeValue(detail.qtm->variantValue(field.name()));
334             QSet<QString> subTypes;
335
336             if (subTypeValue.isNull() && not field.defaultValue().isNull()) {
337                 subTypeValue = field.defaultValue();
338             }
339
340             switch(subTypeValue.type()) {
341             case QVariant::String:
342                 subTypes.insert(subTypeValue.toString());
343                 break;
344
345             case QVariant::StringList:
346                 subTypes.unite(subTypeValue.toStringList().toSet());
347                 break;
348
349             default:
350                 qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
351                         arg(subTypeValue.typeName(), detail.rdf->name(), field.name()));
352                 continue;
353             }
354
355             predicates.append(field.propertyChain().last().iri());
356
357             foreach(const ClassInfoBase &i, field.subTypeClasses()) {
358                 if (subTypes.contains(i.text())) {
359                     addEntity(classes, entities, predicates, i.iri());
360                 }
361             }
362         }
363     }
364
365     return entities;
366 }
367
368 static QVariant
369 findSubTypeValue(const DetailMapping &detail, const QTrackerContactDetailField *const subTypeField)
370 {
371     QVariant value;
372
373     if (0 != subTypeField && subTypeField->hasSubTypeProperties()) {
374         value = detail.qtm->value(subTypeField->name());
375
376         if (QVariant::String != value.type()) {
377             qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
378                     arg(value.typeName(), detail.rdf->name(), subTypeField->name()));
379             value = QVariant();
380         }
381     }
382
383     return value;
384
385 }
386
387 static const QUrl &
388 findSubTypePredicate(const DetailMapping &detail, const QTrackerContactDetailField &field)
389 {
390     const QTrackerContactDetailField *const subTypeField = detail.rdf->subTypeField();
391     const QVariant subTypeValue = findSubTypeValue(detail, subTypeField);
392
393     if (subTypeValue.isValid()) {
394         foreach(const PropertyInfoBase &pi, subTypeField->subTypeProperties()) {
395             if (pi.value() == subTypeValue) {
396                 return pi.iri();
397             }
398         }
399     }
400
401     return field.propertyChain().last().iri();
402 }
403
404 static void
405 appendUpdate(const RDFVariable &subject, const RDFVariable &predicate,
406              const QVariant &value, RDFStatementList &insertStatements)
407 {
408     insertStatements << RDFStatement(subject, predicate, QTrackerContactQueryBuilder::value(value));
409 }
410
411 static QString
412 makeUniqueName(const QString &basename, const RDFStatementList &statements)
413 {
414     return basename + QString::number(statements.count());
415 }
416
417 static void
418 collectFieldUpdates(QContactLocalId contactId,
419                     const RDFVariable &subject, const DetailMapping &detail,
420                     const QTrackerContactDetailField &field, const QVariant &value,
421                     const EntityHash &entities, PredicateVariableHash &objectCache,
422                     RDFStatementList &insertStatements)
423 {
424     if (not value.isValid()) {
425         qctWarn(QString::fromLatin1("Value must be valid for %2 field of %1 detail").
426                 arg(detail.rdf->name(), field.name()));
427         return;
428     }
429
430     if (field.propertyChain().isEmpty()) {
431         qctWarn(QString::fromLatin1("RDF property chain needed for %2 field of %1 detail").
432                 arg(detail.rdf->name(), field.name()));
433         return;
434     }
435
436     if (field.propertyChain().first().readOnly()) {
437         return;
438     }
439
440     RDFVariableList axis;
441     axis.append(subject);
442
443     if (field.propertyChain().count() > 1) {
444         PredicateChain predicates;
445
446         const PropertyInfoList::ConstIterator end(field.propertyChain().end() - 1);
447         for(PropertyInfoList::ConstIterator pi(field.propertyChain().begin()); pi != end; ++pi) {
448             const PropertyInfoList::ConstIterator npi = pi + 1;
449
450             if (pi->readOnly()) {
451                 break;
452             }
453
454             PredicateVariableHash::ConstIterator object(objectCache.find(predicates << pi->iri()));
455
456             if (object == objectCache.end()) {
457                 // figure out subject IRI for this field
458                 QUrl subjectIri;
459
460                 if (npi->hasSubjectScheme()) {
461                     // Prefer the detail's explicit URI for tail nodes, but only if the
462                     // property's subject scheme matches the detail's scheme. This additional
463                     // check is needed because details like Organization randomly spread their
464                     // fields over many different entities.
465                     if ((pi+1) == end && npi->subjectScheme() == detail.rdf->detailScheme()) {
466                         subjectIri = detail.qtm->detailUri();
467                     }
468
469                     // If we don't have an entity IRI yet generate it from property and value.
470                     if (subjectIri.isEmpty()) {
471                         subjectIri = npi->makeSubject(contactId, value);
472                     }
473                 }
474
475                 // name object variable and assign subject IRI
476                 RDFVariable objectVariable;
477
478                 if (subjectIri.isEmpty()) {
479                     // create named blank variable if no subject IRI could be build
480                     QString name(makeUniqueName(detail.rdf->name(), insertStatements));
481                     objectVariable.metaAssign(RDFVariable(name));
482                 } else {
483                     // assign generated subject IRI
484                     objectVariable.metaAssign(subjectIri);
485                 }
486
487                 // assign RDF classes to this field
488                 EntityHash::ConstIterator entity = entities.find(predicates);
489
490                 if (entities.end() == entity) {
491                     qctWarn(QString::fromLatin1("Cannot find entities for %3 property of %1 detail's %2 field").
492                             arg(detail.rdf->name(), field.name(), predicates.last().toString()));
493                 }
494
495                 for(; entity != entities.end() && entity.key() == predicates; ++entity) {
496                     // create insert statement of this entity type
497                     object = objectCache.insert(predicates, objectVariable);
498                     insertStatements << RDFStatement(*object, rdf::type::iri(), *entity.value());
499                 }
500             }
501
502             // create insert statement with the field's value
503             insertStatements << RDFStatement(axis.last(), pi->iri(), *object);
504             axis.append(insertStatements.last().object());
505         }
506     }
507
508     // find proper type of the value to insert
509     appendUpdate(axis.last(), findSubTypePredicate(detail, field), value, insertStatements);
510
511     // insert computed values
512     foreach(const PropertyInfoBase &pi, field.computedProperties()) {
513         QVariant computedValue;
514
515         if (pi.conversion()->makeValue(value, computedValue)) {
516             appendUpdate(axis.last(), pi.iri(), computedValue, insertStatements);
517         }
518     }
519 }
520
521 static void
522 updateDetailUris(QContactLocalId localId, const DetailMappingList &mappings)
523 {
524     foreach(const DetailMapping &detail, mappings) {
525         // skip custom details
526         if (detail.isCustomDetail()) {
527             continue;
528         }
529
530         // update detail URIs
531         const QString oldDetailUri(detail.qtm->detailUri());
532         detail.rdf->updateDetailUri(localId, *detail.qtm);
533         const QString newDetailUri(detail.qtm->detailUri());
534
535         // was up-to-date already? next one please
536         if (oldDetailUri == newDetailUri) {
537             continue;
538         }
539
540         // update details pointing to this one
541         foreach(const DetailMapping &maybeLinked, mappings) {
542             QStringList linkedDetails(maybeLinked.qtm->linkedDetailUris());
543
544             // try to remove old URI from list of linked details
545             if (not linkedDetails.removeOne(oldDetailUri)) {
546                 continue; // detail was not linked, move on
547             }
548
549             // add new URI to list of linked details
550             linkedDetails.append(newDetailUri);
551             maybeLinked.qtm->setLinkedDetailUris(linkedDetails);
552         }
553     }
554 }
555
556 UpdateBuilder::UpdateBuilder(const QTrackerContactSaveRequest *request,
557                              const QContact &contact, const QDateTime &timestamp)
558     : m_request(request)
559     , m_schema(request->engine()->schema(contact.type()))
560     , m_classes(request->engine()->classes())
561 {
562     // figure out the contact's timestamps
563    QContactTimestamp timestampDetail(contact.detail<QContactTimestamp>());
564
565    m_accessed = timestampDetail.variantValue(QContactTimestamp__FieldAccessedTimestamp).toDateTime();
566    m_hadAccessed = not m_accessed.isNull();
567
568    if (m_accessed.isNull()) {
569        m_accessed = timestamp;
570    }
571
572    m_created = timestampDetail.created();
573    m_hadCreated = not m_created.isNull();
574
575    if (m_created.isNull()) {
576        m_created = timestamp;
577    }
578
579    m_lastModified = timestampDetail.lastModified();
580    m_hadLastModified = not m_lastModified.isNull();
581
582    if (m_lastModified.isNull() || 0 != contact.localId()) {
583        // only preserve the lastModified field for new contacts...
584        m_lastModified = timestamp;
585    }
586
587    // figure out the contact's GUID
588    QContactGuid guidDetail(contact.detail<QContactGuid>());
589
590    m_guid = guidDetail.guid();
591    m_hadGuid = not m_guid.isEmpty();
592
593    if (m_guid.isEmpty()) {
594        m_guid = makeContactUID();
595    }
596
597    // calculate local contact ID
598    m_contactLocalId = contact.localId();
599
600    if (0 == m_contactLocalId) {
601        m_contactLocalId = qHash(guid());
602    }
603
604    m_isSelfContact = (m_contactLocalId == request->engine()->selfContactId(0));
605
606    // collect details and update their URIs
607    foreach(const QContactDetail &detail, contact.details()) {
608         const QTrackerContactDetail *definition(m_schema.detail(detail.definitionName()));
609
610         if (0 == definition) {
611             if (m_schema.isSyntheticDetail(detail.definitionName())) {
612                 continue; // don't even store synthesized details as nao:Property
613             }
614         }
615
616         m_mappings.append(DetailMapping(new QContactDetail(detail), definition));
617     }
618
619    updateDetailUris(contactLocalId(), m_mappings);
620 }
621
622 static void
623 collect(RDFStatementList &statements,
624         RDFVariable &subject, const QUrl &predicate, const RDFVariable &value)
625 {
626     statements.append(RDFStatement(subject, predicate, value));
627 }
628
629 static void
630 collect(RDFStatementList &explicitInserts, RDFStatementList &implicitInserts,
631         RDFVariable &subject, const QUrl &predicate, const RDFVariable &value,
632         bool explicitValue = true)
633 {
634     if (explicitValue) {
635         collect(explicitInserts, subject, predicate, value);
636     } else {
637         RDFVariable restricted(subject.metaValue().iri());
638         RDFVariable property(restricted.optional().property(predicate));
639         collect(implicitInserts, restricted, predicate, value);
640         not restricted.variable(property).isBound();
641     }
642 }
643
644 static void
645 insertCustomDetail(RDFStatementList &inserts, RDFVariable &subject, const QContactDetail &detail)
646 {
647     RDFVariable detailProperty(makeUniqueName(detail.definitionName(), inserts));
648
649     collect(inserts, subject, nao::hasProperty::iri(), detailProperty);
650     collect(inserts, detailProperty, rdf::type::iri(), nao::Property::iri());
651     collect(inserts, detailProperty, rdf::type::iri(), nie::InformationElement::iri());
652     collect(inserts, detailProperty, nao::propertyName::iri(),
653             LiteralValue(detail.definitionName()));
654
655     const QVariantMap fields(detail.variantValues());
656
657     for(QVariantMap::ConstIterator i(fields.begin()); i != fields.end(); ++i) {
658         const QString baseName = QTrackerContactQueryBuilder::name(detail, i.key());
659         RDFVariable fieldProperty(makeUniqueName(baseName, inserts));
660
661         collect(inserts, detailProperty, nao::hasProperty::iri(), fieldProperty);
662         collect(inserts, fieldProperty, rdf::type::iri(), nao::Property::iri());
663         collect(inserts, fieldProperty, nao::propertyName::iri(), LiteralValue(i.key()));
664         collect(inserts, fieldProperty, nao::propertyValue::iri(), LiteralValue(i.value()));
665     }
666 }
667
668 void
669 UpdateBuilder::collectInsertions(RDFUpdate &update) const
670 {
671     RDFStatementList explicitInserts, implicitInserts;
672     RDFVariable affilination;
673
674     // Removing Role properties also removed the contact's PersonContact type,
675     // so we have to restore it.
676     RDFVariable subject(schema().makeContactIri(contactLocalId()));
677
678     // Restore the resource type, we removed it for quick propertly deletion.
679     foreach(const QUrl &classIri, schema().contactClassIris()) {
680         collect(explicitInserts, subject, rdf::type::iri(), classIri);
681     }
682
683     // Always update the local UID to support a-priori contacts like the self contact.
684     // Accept update errors if that property already exists with different value as
685     // this property is assumed to be immutable.
686     const LiteralValue contactLocalUID(QString::number(contactLocalId()));
687     collect(explicitInserts, subject, nco::contactLocalUID::iri(), contactLocalUID);
688
689     // Collect inserts for each regular detail
690     foreach(const DetailMapping &detail, mappings()) {
691         // store custom details via nao:Property
692         if (detail.isCustomDetail()) {
693             insertCustomDetail(explicitInserts, subject, *detail.qtm);
694             continue;
695         }
696
697         // don't save synthesized details
698         if (detail.isSyntheticDetail()) {
699             continue;
700         }
701
702         const QVariantMap detailValues = detail.qtm->variantValues();
703         const EntityHash entities = collectEntities(classes(), detail);
704         const bool isAffiliatedDetail = detail.isAffiliatedDetail();
705         const bool isPersonalDetail = detail.isPersonalDetail();
706         PredicateVariableHash objectCache;
707
708         foreach(const QTrackerContactDetailField &field, detail.rdf->fields()) {
709             if (field.hasSubTypes()) {
710                 // Subtypes are stored indirectly by assigning the proper resource class
711                 // to the RDF resource representing this field, or by selecting the matching
712                 // RDF property for storing the detail value.
713                 continue;
714             }
715
716             const QVariantMap::ConstIterator fieldValue = detailValues.find(field.name());
717
718             QVariant rdfValue;
719
720             if (fieldValue == detailValues.end() ||
721                 not field.makeValue(*fieldValue, rdfValue)) {
722                 // Could not compute the RDF value for this field.
723                 continue;
724             }
725
726             if (isPersonalDetail) {
727                 collectFieldUpdates(contactLocalId(), subject, detail, field,
728                                     rdfValue, entities, objectCache, explicitInserts);
729             }
730
731             if (isAffiliatedDetail) {
732                 // create nco::Affiliation entity when needed
733                 if (not affilination.metaIsDefinite()) {
734                     affilination.metaAssign(makeAffiliationIri(contactLocalId()));
735
736                     collect(explicitInserts, affilination, rdf::type::iri(), nco::Affiliation::iri());
737                     collect(explicitInserts, subject, nco::hasAffiliation::iri(), affilination);
738                 }
739
740                 collectFieldUpdates(contactLocalId(), affilination, detail, field,
741                                     rdfValue, entities, objectCache, explicitInserts);
742             }
743         }
744     }
745
746     collect(explicitInserts, subject,
747             nie::contentLastModified::iri(),
748             LiteralValue(lastModified()));
749     collect(explicitInserts, implicitInserts,
750             subject, nie::contentCreated::iri(),
751             LiteralValue(created()), hadCreated());
752     collect(explicitInserts, implicitInserts,
753             subject, nco::contactUID::iri(),
754             LiteralValue(guid()), hadGuid());
755
756     update.addInsertion(explicitInserts);
757
758     foreach(const RDFStatement &statement, implicitInserts) {
759         update.addInsertion(statement);
760     }
761 }
762
763 void
764 UpdateBuilder::deleteContactProperties(RDFUpdate &update) const
765 {
766     RDFVariable contact = schema().makeContactIri(contactLocalId());
767
768     RDFVariable domain(QString::fromLatin1("d"));
769     RDFVariable predicate(QString::fromLatin1("p"));
770     RDFVariable object(QString::fromLatin1("o"));
771
772     // Only remove properties of inherited classes in managed ontologies
773     domain.isMemberOf(iriVariableList(schema().inheritedClassIris()));
774
775     predicate.property<rdfs::domain>(domain);
776     predicate.notEqual(nco::contactLocalUID::iri());
777
778     // Remove all properties of Role, Contact and PersonContact domain,
779     // except for local id and globally unique id.
780     if (not hadGuid()) {
781         predicate.notEqual(nco::contactUID::iri());
782     }
783
784     // NB#177560 - skip removing nco::IMAddress for self Contact
785     if (isSelfContact()) {
786        predicate.notEqual(nco::hasIMAddress::iri());
787     }
788
789     update.addDeletion(contact, predicate, object);
790
791     // Remove timestamps when needed.
792     if (hadAccessed()) {
793         update.addDeletion(contact, nie::contentAccessed::iri());
794     }
795
796     if (hadCreated()) {
797         update.addDeletion(contact, nie::contentCreated::iri());
798     }
799
800     update.addDeletion(contact, nie::contentLastModified::iri());
801
802     // Remove tags
803     update.addDeletion(contact, nao::hasTag::iri());
804 }
805
806 void
807 UpdateBuilder::deleteCustomDetails(RDFUpdate &update) const
808 {
809     RDFVariable contact = schema().makeContactIri(contactLocalId());
810     RDFVariable customDetail(QString::fromLatin1("customDetail"));
811     RDFVariable customField(QString::fromLatin1("customField"));
812
813     update.addDeletion(RDFStatementList() <<
814                        RDFStatement(customField, rdf::type::iri(), nao::Property::iri()) <<
815                        RDFStatement(customDetail, rdf::type::iri(), nao::Property::iri()) <<
816                        RDFStatement(customDetail, rdf::type::iri(), rdfs::Resource::iri()) <<
817                        RDFStatement(contact, nao::hasProperty::iri(), customDetail));
818
819     contact.property<nao::hasProperty>(customDetail);
820     customDetail.property<nao::hasProperty>(customField);
821 }
822
823 void
824 UpdateBuilder::deleteAssociatedObjects(RDFUpdate &update) const
825 {
826     RDFVariable contact = schema().makeContactIri(contactLocalId());
827
828     // Remove all affiliation and organization properties in one go.
829     // Initially we limited to nco:Role instead of rdf:Resource to only hit
830     // "owned" properties, but this causes trouble with properties like
831     // nco:fullname which for instance also implements dc:title.
832     RDFVariable affiliation = makeAffiliationIri(contactLocalId());
833     update.addDeletion(affiliation, rdf::type::iri(), rdfs::Resource::iri());
834
835     RDFVariable organization = makeOrganizationIri(contactLocalId());
836     update.addDeletion(organization, rdf::type::iri(), rdfs::Resource::iri());
837
838     // Reset subtypes of associated entities
839     foreach(const QTrackerContactDetail &definition, schema().details()) {
840         const QTrackerContactDetailField *const subTypeField = definition.subTypeField();
841         if (0 == subTypeField || not subTypeField->hasSubTypes()) {
842             continue;
843         }
844
845         const QTrackerContactDetailField *const subjectField = definition.subjectField();
846         if (0 == subjectField || not subjectField->hasPropertyChain()) {
847             continue;
848         }
849
850         const PropertyInfoBase &subjectProperty = subjectField->propertyChain().last();
851         QTrackerContactSubject::Scheme scheme = subjectProperty.domainScheme();
852
853         if (not QTrackerContactSubject::isContentScheme(scheme)) {
854             // TODO Check if this skipping makes any sense. At least for address details
855             // this skipping dramatically pollutes the tracker store.
856             qctWarn(QString::fromLatin1("Skipping deletion of %1 detail").arg(definition.name()));
857             continue;
858         }
859
860         foreach(const DetailMapping &mapping, mappings()) {
861             if (mapping.qtm->definitionName() != definition.name()) {
862                 continue;
863             }
864
865             const QVariant value = mapping.qtm->value(subjectField->name());
866
867             if (value.isNull()) {
868                 continue;
869             }
870
871             update.addDeletion(QTrackerContactSubject::makeIri(scheme, contactLocalId(),
872                                                                QVariantList() << value),
873                                rdf::type::iri(), subjectProperty.domainIri());
874         }
875     }
876 }
877
878 void
879 UpdateBuilder::collectDeletions(RDFUpdate &update) const
880 {
881     deleteContactProperties(update);
882     deleteCustomDetails(update);
883     deleteAssociatedObjects(update);
884 }
885
886 void
887 QTrackerContactSaveRequest::normalizeCurrentContact()
888 {
889     QContact &contact = m_contacts[m_contactOffset];
890
891     foreach(const QTrackerContactDetail &detail, engine()->schema(contact.type()).details()) {
892         if (not detail.isUnique()) {
893             continue;
894         }
895
896         QList<QContactDetail> contactDetails(contact.details(detail.name()));
897
898         if (contactDetails.count() < 2) {
899             continue;
900         }
901
902         qctWarn(QString::fromLatin1("Dropping odd details for contact %1: "
903                                     "%2 detail must be unique").
904                 arg(m_contactOffset).arg(detail.name()));
905
906         for(int i = 1; i < contactDetails.count(); ++i) {
907             contact.removeDetail(&contactDetails[i]);
908         }
909     }
910 }
911
912 static QString
913 makeAvatarCacheFilePath(const QContact& contact)
914 {
915     // FIXME: contacts needs to fix loading of images from URI when not inside
916     // their own cache directory
917     // Not using Qt function to get cache dir since it is in QtGui...
918     static const QLatin1String avatarCachePath(".cache/contacts/photos");
919
920     QString uuidString;
921
922     // Use contact's uuid if present
923     QContactGuid contactGuid = contact.detail<QContactGuid>();
924     QUuid guid = QUuid(contactGuid.guid());
925     if (guid.isNull()) {
926         uuidString = QUuid::createUuid().toString();
927     } else {
928         // Only take contact's uuid if it was a real one
929         uuidString = guid.toString();
930     }
931     uuidString = uuidString.mid(1, uuidString.length() - 2);
932
933     QDir cache = QDir::home();
934     cache.cd(avatarCachePath);
935     if (not cache.exists()) {
936         cache.mkpath(cache.absolutePath());
937     }
938
939     return cache.absoluteFilePath(QString::fromLatin1("%1.png").arg(uuidString));
940 }
941
942 void
943 QTrackerContactSaveRequest::normalizeAvatars()
944 {
945     QContact &contact = m_contacts[m_contactOffset];
946     bool substitute = true;
947     QStringList avatarLinkedDetails;
948
949     // normalizeCurrentContact drops all but one avatar
950     const QContactAvatar &avatar = contact.detail(QContactAvatar::DefinitionName);
951     avatarLinkedDetails = avatar.linkedDetailUris();
952
953     if (not avatarLinkedDetails.isEmpty()) {
954         // get online accounts associated with this contact to see if one is
955         // linked with avatar
956         QList<QContactDetail> onlineAccounts = contact.details(QContactOnlineAccount::DefinitionName);
957         if (not onlineAccounts.isEmpty()) {
958             foreach (const QContactDetail &detail, onlineAccounts) {
959                 if (avatarLinkedDetails.contains(detail.detailUri())) {
960                     substitute = false;
961
962                     break;
963                 }
964             }
965         }
966     }
967
968     // get first thumbnail from contact and convert it into an QContactAvatar
969     // get all thumbnails here because we drop all of them later
970     QList<QContactDetail> thumbs = contact.details(QContactThumbnail::DefinitionName);
971     if (thumbs.length() > 0) {
972         if (substitute) {
973             const QImage &thumb = static_cast<const QContactThumbnail&>(thumbs.at(0)).thumbnail();
974             QImage scaled = thumb.scaled (QTrackerContactSettings().avatarSize(),
975                                           Qt::KeepAspectRatio);
976
977             QString cachedAvatar = makeAvatarCacheFilePath(contact);
978             if (scaled.save(cachedAvatar)) {
979                 QContactAvatar avatarFromThumb;
980                 avatarFromThumb.setImageUrl(QUrl::fromLocalFile(cachedAvatar));
981                 contact.saveDetail(&avatarFromThumb);
982
983                 if (not avatar.isEmpty()) {
984                     contact.removeDetail(const_cast<QContactAvatar*>(&avatar));
985                 }
986             }
987         }
988
989         // drop all thumbnails
990         foreach (const QContactDetail &thumb, thumbs) {
991             contact.removeDetail(const_cast<QContactDetail*>(&thumb));
992         }
993     }
994 }
995
996 RDFUpdate
997 QTrackerContactSaveRequest::buildQuery(QContact &contact)
998 {
999     UpdateBuilder builder(this, contact, timestamp());
1000
1001     // Create local contact id when needed
1002     QContactId id = contact.id();
1003     id.setLocalId(builder.contactLocalId());
1004     id.setManagerUri(engine()->managerUri());
1005     contact.setId(id);
1006
1007     // Build the update query
1008     RDFUpdate update;
1009
1010     builder.collectDeletions(update);
1011     builder.collectInsertions(update);
1012
1013     return update;
1014 }
1015
1016 ///////////////////////////////////////////////////////////////////////////////////////////////////
1017
1018 void
1019 QTrackerContactSaveRequest::saveContact()
1020 {
1021     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1022         qDebug()
1023                 << metaObject()->className() << m_stopWatch.elapsed()
1024                 << ": contact" << m_contactOffset << "- updating";
1025     }
1026
1027     // drop odd details
1028     normalizeCurrentContact();
1029
1030     normalizeAvatars();
1031
1032     // build the update query and run it
1033     const RDFUpdate query = buildQuery(m_contacts[m_contactOffset]);
1034     const QString queryString = ::BackEnds::Tracker::tracker()->rawQueryString(query);
1035
1036     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowUpdates)) {
1037         qDebug() << queryString;
1038     }
1039
1040     QDBusPendingCall updateCall(Tracker::Resources::
1041                                 batchSparqlUpdate(m_trackerInstance, queryString,
1042                                                   engine()->trackerTimeout()));
1043
1044     PendingUpdatePtr pendingUpdate(new QTrackerContactUpdate(updateCall, m_contactOffset));
1045     connect(pendingUpdate.data(), SIGNAL(failure(int,QString)), SLOT(failure(int,QString)));
1046     connect(pendingUpdate.data(), SIGNAL(success(int)), SLOT(success(int)));
1047     m_pendingUpdates.insert(m_contactOffset, pendingUpdate);
1048
1049     if (++m_batchSize >= engine()->batchSize()) {
1050         commit();
1051     }
1052 }
1053
1054 void
1055 QTrackerContactSaveRequest::proceed()
1056 {
1057     // Too many updates are running still, let's abort.
1058     // We'll get called again when next pending update is commited.
1059     if (m_pendingUpdates.count() >= engine()->concurrencyLevel() / 2 &&
1060         m_pendingUpdates.count() > 0) {
1061         return;
1062     }
1063
1064     while (m_contactOffset < m_contacts.count()) {
1065         // Now we have queued too many updates, let's abort.
1066         // We'll get called again when next pending update is commited.
1067         if (m_pendingUpdates.count() >= engine()->concurrencyLevel()) {
1068             return;
1069         }
1070
1071         saveContact();
1072         m_contactOffset++;
1073     }
1074
1075     if (hasPendingUpdates()) {
1076         commit(); // We left the saveContact() loop, and now must commit the queued updates.
1077         return;
1078     }
1079
1080     // We left the saveContact() loop, and don't have any pending updates or
1081     // pending commits. This shows the save request was finished. Let's report this.
1082     if (not hasPendingCommit()) {
1083         if (m_errorMap.isEmpty()) {
1084             m_effectiveError = QContactManager::NoError;
1085         }
1086
1087         emitResult(m_effectiveError);
1088     }
1089 }
1090
1091 void
1092 QTrackerContactSaveRequest::commit()
1093 {
1094     // Don't do anything if another commit is running still.
1095     if (hasPendingCommit()) {
1096         return;
1097     }
1098
1099     // Dump some debug information when requested.
1100     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1101         qDebug()
1102                 << metaObject()->className() << m_stopWatch.elapsed()
1103                 << "- commiting" << m_batchSize << "pending updates";
1104     }
1105
1106     // Tell tracker to commit queued updates.
1107     QDBusPendingCall commitCall(Tracker::Resources::batchCommit(m_trackerInstance,
1108                                                                 engine()->trackerTimeout()));
1109
1110     m_commit.reset(new QDBusPendingCallWatcher(commitCall));
1111
1112     connect(m_commit.data(),
1113             SIGNAL(finished(QDBusPendingCallWatcher*)),
1114             SLOT(commitFinished()));
1115
1116     m_batchSize = 0;
1117 }
1118
1119 void
1120 QTrackerContactSaveRequest::commitFinished()
1121 {
1122     if (m_commit->isError()) {
1123         // Seems we don't have to do anything for for this error except for printing
1124         // it to console: We won't get a success signal for the failed update, and
1125         // therefore the failed contact should stay in m_errorMap[] causing proper
1126         // error reporting.
1127         qctWarn(QString::fromLatin1("Commit failed: %1").arg(m_commit->error().message()));
1128     }
1129
1130
1131     m_commit.reset(); // Mark this commit as done.
1132     proceed(); // Check if other updates are pending, or if more contacts must be written.
1133 }
1134
1135 void
1136 QTrackerContactSaveRequest::success(int offset)
1137 {
1138     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1139         qDebug()
1140                 << metaObject()->className() << m_stopWatch.elapsed()
1141                 << ": contact" << offset << " - update succeded";
1142     }
1143
1144     m_pendingUpdates.remove(offset); // Mark this update as done.
1145     m_errorMap.remove(offset); // Mark this update's contact as successfully written.
1146     proceed(); // Check if other updates are pending, or if more contacts must be written.
1147 }
1148
1149 void
1150 QTrackerContactSaveRequest::failure(int offset, QString const &message)
1151 {
1152     qctWarn(QString::fromLatin1("Failed to update contact %1: %2").arg(offset).arg(message));
1153
1154     m_pendingUpdates.remove(offset); // Mark this update as done.
1155     proceed(); // Check if other updates are pending, or if more contacts must be written.
1156
1157     // Notice: We don't remove the update's contact from m_errorMap[]
1158     // which results in proper error reporting.
1159 }
1160
1161 ///////////////////////////////////////////////////////////////////////////////////////////////////
1162
1163 bool
1164 QTrackerContactSaveRequest::hasPendingUpdates() const
1165 {
1166     return not m_pendingUpdates.isEmpty();
1167 }
1168
1169 bool
1170 QTrackerContactSaveRequest::hasPendingCommit() const
1171 {
1172     return not m_commit.isNull();
1173 }
1174
1175 ///////////////////////////////////////////////////////////////////////////////////////////////////
1176
1177 bool
1178 QTrackerContactSaveRequest::start()
1179 {
1180     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1181         qDebug()
1182                 << metaObject()->className() << 0
1183                 << ": number of contacts:" << m_request->contacts().count();
1184     }
1185
1186     m_stopWatch.start();
1187
1188     // prepare error map with default values
1189     for(int i = 0; i < m_contacts.count(); ++i) {
1190         m_errorMap.insert(i, QContactManager::UnspecifiedError);
1191     }
1192
1193     // find tracker instance
1194     QDBusConnectionInterface *const dbusInterface(QTrackerDBusConnectionManager::sessionBus()->interface());
1195     QDBusReply<QString> reply(dbusInterface->serviceOwner(Tracker::ServiceName));
1196
1197     if (reply.error().type() != QDBusError::NoError) {
1198         qctWarn(QString::fromLatin1("tracker service not found: %1").arg(reply.error().message()));
1199         return false;
1200     }
1201
1202     m_trackerInstance = reply.value();
1203
1204     // start operation
1205     m_contactOffset = 0;
1206     QTimer::singleShot(0, this, SLOT(proceed()));
1207
1208     return true;
1209 }
1210
1211 void
1212 QTrackerContactSaveRequest::emitResult(QContactManager::Error error)
1213 {
1214     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowTiming)) {
1215         qDebug()
1216                 << metaObject()->className()  << m_stopWatch.elapsed()
1217                 << ": reporting result" << error;
1218     }
1219
1220     engine()->updateContactSaveRequest(m_request, m_contacts, error, m_errorMap,
1221                                       QContactAbstractRequest::FinishedState);
1222 }
1223
1224 ///////////////////////////////////////////////////////////////////////////////////////////////////
1225
1226 #include "moc_contactsaverequest.cpp"