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