1 /****************************************************************************
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the Qt Mobility Components.
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
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.
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.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
42 #include "contactsaverequest.h"
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>
54 #include <dbus/tracker.h>
56 #include <QtGui/QImageWriter>
58 #include <QtTracker/ontologies/nao.h>
59 #include <QtTracker/ontologies/nie.h>
63 ///////////////////////////////////////////////////////////////////////////////////////////////////
65 using namespace SopranoLive;
66 using namespace SopranoLive::Ontologies;
68 ///////////////////////////////////////////////////////////////////////////////////////////////////
70 typedef QList<class DetailMapping> DetailMappingList;
71 typedef QMultiHash<PredicateChain, const QUrl *> EntityHash;
73 ///////////////////////////////////////////////////////////////////////////////////////////////////
75 class DetailMappingData : public QSharedData
77 friend class DetailMapping;
79 public: // constructors
80 DetailMappingData(const QContactDetail &genericDetail,
81 const QTrackerContactDetail *trackerDetail)
82 : m_genericDetail(genericDetail)
83 , m_trackerDetail(trackerDetail)
88 QContactDetail m_genericDetail;
89 const QTrackerContactDetail * m_trackerDetail;
92 ///////////////////////////////////////////////////////////////////////////////////////////////////
96 public: // constructors
97 DetailMapping(const QContactDetail &genericDetail,
98 const QTrackerContactDetail *trackerDetail)
99 : d(new DetailMappingData(genericDetail, trackerDetail))
103 public: // attributes
104 /// a detail which has no own ontology representation
105 bool isCustomDetail() const;
107 /// is a detail which is generated from other details and not stored itself
108 bool isSyntheticDetail() const;
110 /// is a detail which belongs to an affiliation, see "Work" context
111 bool isAffiliatedDetail() const;
113 /// is a detail which is directly belongs to the person, see "Home" context
114 bool isPersonalDetail() const;
116 QString definitionName() const;
118 QString detailUri() const;
119 QStringList linkedDetailUris() const;
121 QVariant fieldValue(const QString &fieldName) const;
122 QVariantMap fieldValues() const;
124 const QList<QTrackerContactDetailField> & trackerFields() const;
125 const QTrackerContactDetailField * subTypeField() const;
126 QTrackerContactSubject::Scheme detailScheme() const;
129 bool updateDetailUri(QContactLocalId contactId, const DetailMappingList &mappings);
132 QExplicitlySharedDataPointer<DetailMappingData> d;
135 ///////////////////////////////////////////////////////////////////////////////////////////////////
140 UpdateBuilder(const QTrackerContactSaveRequest *request,
141 const QContact &contact, const QDateTime ×tamp);
143 const QString & guid() const { return m_guid; }
144 bool hadGuid() const { return m_hadGuid; }
146 const QDateTime & accessed() const { return m_accessed; }
147 bool hadAccessed() const { return m_hadAccessed; }
149 const QDateTime & created() const { return m_created; }
150 bool hadCreated() const { return m_hadCreated; }
152 const QDateTime & lastModified() const { return m_lastModified; }
153 bool hadLastModified() const { return m_hadLastModified; }
155 const DetailMappingList & mappings() const { return m_mappings; }
156 const QContactLocalId & contactLocalId() const { return m_contactLocalId; }
157 const QTrackerContactDetailSchema & schema() const { return m_schema; }
158 const QTrackerClassHierarchy & classes() const { return m_classes; }
160 bool isSelfContact() const { return m_isSelfContact; }
163 void collectInsertions(RDFUpdate &update);
164 void collectDeletions(RDFUpdate &update) const;
167 QUrl makeIri(QTrackerContactSubject::Scheme scheme, const QVariantList &values) const;
168 QString makeUniqueName(const QString &basename) const;
170 void insert(const RDFVariable &subject, const RDFVariable &predicate,
171 const RDFVariable &value, bool explicitValue = true);
173 void insertCustomValues(const RDFVariable &subject, const QString &detailName,
174 const QString &fieldName, const QVariant &value,
175 const QVariantList &allowableValues = QVariantList());
176 void insertCustomValues(const RDFVariable &subject, const QString &detailName,
177 const QTrackerContactDetailField &field, const QVariant &value);
179 void insertCustomDetail(RDFVariable &subject, const DetailMapping &detail);
181 void insertDetailField(QContactLocalId contactId,
182 const RDFVariable &subject, const DetailMapping &detail,
183 const QTrackerContactDetailField &field, const QVariant &value,
184 const EntityHash &entities, PredicateVariableHash &objectCache);
186 void deleteContactProperties(RDFUpdate &update) const;
187 void deleteAssociatedObjects(RDFUpdate &update) const;
188 void deleteCustomDetails(RDFUpdate &update) const;
190 const QctLogger & qctLogger() const { return m_request->qctLogger(); }
193 const QTrackerContactSaveRequest *const m_request;
196 QDateTime m_accessed;
198 QDateTime m_lastModified;
199 DetailMappingList m_mappings;
200 QContactLocalId m_contactLocalId;
201 const QTrackerContactDetailSchema &m_schema;
202 const QTrackerClassHierarchy &m_classes;
204 RDFStatementList m_explicitInserts;
205 RDFStatementList m_implicitInserts;
208 bool m_hadAccessed : 1;
209 bool m_hadCreated : 1;
210 bool m_hadLastModified : 1;
211 bool m_isSelfContact : 1;
214 ///////////////////////////////////////////////////////////////////////////////////////////////////
216 class QTrackerContactSaveRequestTask : public QObject
218 Q_DISABLE_COPY(QTrackerContactSaveRequestTask);
222 QTrackerContactSaveRequestTask(const QDBusPendingCall &call, int offset, QObject *parent = 0);
225 void success(int offset);
226 void failure(int offset, QString const &message);
232 QDBusPendingCallWatcher m_call;
236 ///////////////////////////////////////////////////////////////////////////////////////////////////
238 template <typename T>
239 inline bool operator<(const QList<T> &a, const QList<T> &b)
241 for(int i = 0; i < a.count() && i < b.count(); ++i) {
244 } else if (b[i] < a[i]) {
249 return a.count() < b.count();
252 ///////////////////////////////////////////////////////////////////////////////////////////////////
255 DetailMapping::isCustomDetail() const
257 return (0 == d->m_trackerDetail);
261 DetailMapping::isSyntheticDetail() const
263 return (0 != d->m_trackerDetail && d->m_trackerDetail->isSynthetic());
267 DetailMapping::isAffiliatedDetail() const
269 return (d->m_trackerDetail->hasContext() &&
270 d->m_genericDetail.contexts().contains(QContactDetail::ContextWork));
274 DetailMapping::isPersonalDetail() const
276 if (not d->m_trackerDetail->hasContext()) {
280 const QStringList contexts = d->m_genericDetail.contexts();
282 return (contexts.contains(QContactDetail::ContextHome) ||
283 not contexts.contains(QContactDetail::ContextWork));
287 DetailMapping::definitionName() const
289 return d->m_genericDetail.definitionName();
293 DetailMapping::detailUri() const
295 return d->m_genericDetail.detailUri();
299 DetailMapping::linkedDetailUris() const
301 return d->m_genericDetail.linkedDetailUris();
305 DetailMapping::fieldValue(const QString &fieldName) const
307 return d->m_genericDetail.variantValue(fieldName);
311 DetailMapping::fieldValues() const
313 return d->m_genericDetail.variantValues();
316 inline const QList<QTrackerContactDetailField> &
317 DetailMapping::trackerFields() const
319 return d->m_trackerDetail->fields();
322 inline const QTrackerContactDetailField *
323 DetailMapping::subTypeField() const
325 return d->m_trackerDetail->subTypeField();
328 inline QTrackerContactSubject::Scheme
329 DetailMapping::detailScheme() const
331 return d->m_trackerDetail->detailScheme();
335 DetailMapping::updateDetailUri(QContactLocalId contactId, const DetailMappingList &mappings)
337 // skip custom details
338 if (isCustomDetail()) {
342 QTrackerContactSubject::Scheme scheme = QTrackerContactSubject::Anonymous;
343 const QString oldDetailUri = detailUri();
346 // build new detail URI from current detail content
347 foreach(const QTrackerContactDetailField &f, trackerFields()) {
348 if (f.hasDetailUri()) {
349 if (values.isEmpty()) {
350 scheme = f.propertyChain().last().subjectScheme();
353 values.append(fieldValue(f.name()));
357 // detail URI doesn't use field values, abort
358 if (values.isEmpty()) {
362 // construct new detail URI
363 const QUrl subjectIri = QTrackerContactSubject::makeIri(scheme, contactId, values);
364 const QString newDetailUri = subjectIri.toString();
366 // abort if nothing changed
367 if (newDetailUri == oldDetailUri) {
372 d->m_genericDetail.setDetailUri(newDetailUri);
374 // update details pointing to this one
375 foreach(const DetailMapping &otherDetail, mappings) {
376 QStringList linkedDetails = otherDetail.linkedDetailUris();
378 // try to remove old URI from list of linked details
379 if (linkedDetails.removeOne(oldDetailUri)) {
380 // detail was linked, therefore update the link
381 otherDetail.d->m_genericDetail.setLinkedDetailUris(linkedDetails << newDetailUri);
388 ////////////////////////////////////////////////////////////////////////////////////////////////////
390 QTrackerContactSaveRequestTask::QTrackerContactSaveRequestTask(const QDBusPendingCall &call,
391 int offset, QObject *parent)
396 connect(&m_call, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(finished()));
400 QTrackerContactSaveRequestTask::finished()
402 if (m_call.isError()) {
403 emit failure(m_offset, m_call.error().message());
405 emit success(m_offset);
409 ///////////////////////////////////////////////////////////////////////////////////////////////////
411 QTrackerContactSaveRequest::QTrackerContactSaveRequest(QContactAbstractRequest *request,
412 QContactTrackerEngine *engine,
414 QTrackerBaseRequest<QContactSaveRequest>(request, engine, parent),
415 m_timestamp(QDateTime::currentDateTime()),
416 m_effectiveError(QContactManager::UnspecifiedError),
417 m_contactOffset(0), m_batchSize(0)
419 m_contacts.append(m_request->contacts());
422 QTrackerContactSaveRequest::~QTrackerContactSaveRequest()
426 ///////////////////////////////////////////////////////////////////////////////////////////////////
429 addEntity(const QTrackerClassHierarchy &classes, EntityHash &entities,
430 const PredicateChain &predicates, const QUrl &newIri)
432 foreach(const QUrl *const oldIri, entities.values(predicates)) {
433 if (classes.isSubClassOf(*oldIri, newIri)) {
434 // skipping already covered entity type
438 if (classes.isSubClassOf(newIri, *oldIri)) {
439 // removing too generic entity type which is covered by newIri
440 entities.remove(predicates, oldIri);
444 // adding the new entity type
445 entities.insertMulti(predicates, &newIri);
449 collectEntities(const QTrackerClassHierarchy &classes, const DetailMapping &detail)
453 foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
454 if (field.propertyChain().isEmpty()) {
458 PredicateChain predicates;
460 const PropertyInfoList::ConstIterator end(field.propertyChain().end() - 1);
461 for(PropertyInfoList::ConstIterator i(field.propertyChain().begin()); i != end; ++i) {
462 const QUrl &predicateIri(i->iri());
463 const PropertyInfoBase &pi(*(i + 1));
466 addEntity(classes, entities, predicates << predicateIri, pi.rangeIri());
468 addEntity(classes, entities, predicates << predicateIri, pi.domainIri());
472 if (field.hasSubTypeClasses()) {
473 QVariant subTypeValue(detail.fieldValue(field.name()));
474 QSet<QString> subTypes;
476 if (subTypeValue.isNull() && not field.defaultValue().isNull()) {
477 subTypeValue = field.defaultValue();
480 switch(subTypeValue.type()) {
481 case QVariant::String:
482 subTypes.insert(subTypeValue.toString());
485 case QVariant::StringList:
486 subTypes.unite(subTypeValue.toStringList().toSet());
490 qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
491 arg(subTypeValue.typeName(), detail.definitionName(), field.name()));
495 predicates.append(field.propertyChain().last().iri());
497 foreach(const ClassInfoBase &i, field.subTypeClasses()) {
498 if (subTypes.contains(i.text())) {
499 addEntity(classes, entities, predicates, i.iri());
509 findSubTypeValue(const DetailMapping &detail, const QTrackerContactDetailField *const subTypeField)
513 if (0 != subTypeField && subTypeField->hasSubTypeProperties()) {
514 value = detail.fieldValue(subTypeField->name());
516 if (QVariant::String != value.type()) {
517 qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
518 arg(value.typeName(), detail.definitionName(), subTypeField->name()));
528 findSubTypePredicate(const DetailMapping &detail, const QTrackerContactDetailField &field,
529 const QTrackerContactDetailField *const subTypeField)
531 const QVariant subTypeValue = findSubTypeValue(detail, subTypeField);
533 if (subTypeValue.isValid()) {
534 foreach(const PropertyInfoBase &pi, subTypeField->subTypeProperties()) {
535 if (pi.value() == subTypeValue) {
541 return field.propertyChain().last().iri();
545 UpdateBuilder::makeUniqueName(const QString &basename) const
547 return basename + QString::number(m_explicitInserts.count());
551 UpdateBuilder::insertCustomValues(const RDFVariable &subject,
552 const QString &detailName, const QString &fieldName,
553 const QVariant &value, const QVariantList &allowableValues)
555 QVariantList customValues;
557 switch(value.type()) {
558 case QVariant::StringList:
559 foreach(const QString &element, value.toStringList()) {
560 if (not element.isEmpty() && not allowableValues.contains(element)) {
561 customValues.append(element);
568 foreach(const QVariant &element, value.toList()) {
569 if (not element.isNull() && not allowableValues.contains(element)) {
570 customValues.append(element);
577 if (not value.isNull() && not allowableValues.contains(value)) {
578 customValues.append(value);
582 foreach(const QVariant &element, customValues) {
583 RDFVariable property(makeUniqueName(QTrackerContactQueryBuilder::name(detailName, fieldName)));
585 insert(subject, rdf::type::iri(), nie::InformationElement::iri());
586 insert(subject, nao::hasProperty::iri(), property);
588 insert(property, rdf::type::iri(), nao::Property::iri());
589 insert(property, nao::propertyName::iri(), LiteralValue(fieldName));
590 insert(property, nao::propertyValue::iri(), QTrackerContactQueryBuilder::value(element));
595 UpdateBuilder::insertCustomValues(const RDFVariable &subject, const QString &detailName,
596 const QTrackerContactDetailField &field,
597 const QVariant &value)
599 insertCustomValues(subject, detailName, field.name(), value,
600 field.describeAllowableValues());
603 static PropertyInfoList::ConstIterator
604 endOfPropertyChain(const QTrackerContactDetailField &field)
606 if (field.isWithoutMapping()) {
607 return field.propertyChain().end();
610 return field.propertyChain().end() - 1;
614 UpdateBuilder::insertDetailField(QContactLocalId contactId,
615 const RDFVariable &subject, const DetailMapping &detail,
616 const QTrackerContactDetailField &field, const QVariant &value,
617 const EntityHash &entities, PredicateVariableHash &objectCache)
619 if (not value.isValid()) {
620 qctWarn(QString::fromLatin1("Value must be valid for %2 field of %1 detail").
621 arg(detail.definitionName(), field.name()));
625 const QTrackerContactDetailField *const subTypeField = detail.subTypeField();
626 const PropertyInfoList &propertyChain = field.propertyChain();
628 if (propertyChain.isEmpty()) {
629 qctWarn(QString::fromLatin1("RDF property chain needed for %2 field of %1 detail").
630 arg(detail.definitionName(), field.name()));
634 if (propertyChain.first().readOnly()) {
638 PredicateChain predicateChain;
639 RDFVariableList axis;
640 axis.append(subject);
642 const PropertyInfoList::ConstIterator end = endOfPropertyChain(field);
643 const PropertyInfoList::ConstIterator tail = propertyChain.end() - 1;
645 for(PropertyInfoList::ConstIterator pi = propertyChain.begin(); pi != end; ++pi) {
646 if (pi->readOnly()) {
650 PredicateVariableHash::ConstIterator object = objectCache.find(predicateChain << pi->iri());
652 if (object == objectCache.end()) {
653 // figure out subject IRI for this field
656 const PropertyInfoList::ConstIterator npi = pi + 1;
658 if (npi != propertyChain.end() && npi->hasSubjectScheme()) {
659 // Prefer the detail's explicit URI for tail nodes, but only if the
660 // property's subject scheme matches the detail's scheme. This additional
661 // check is needed because details like Organization randomly spread their
662 // fields over many different entities.
663 if (npi == tail && npi->subjectScheme() == detail.detailScheme()) {
664 subjectIri = detail.detailUri();
667 // If we don't have an entity IRI yet generate it from property and value.
668 if (subjectIri.isEmpty()) {
669 subjectIri = npi->makeSubject(contactId, value);
673 // name object variable and assign subject IRI
674 RDFVariable objectVariable;
676 if (subjectIri.isEmpty()) {
677 // create named blank variable if no subject IRI could be build
678 const QString name = makeUniqueName(detail.definitionName());
679 objectVariable.metaAssign(RDFVariable(name));
681 // assign generated subject IRI
682 objectVariable.metaAssign(subjectIri);
685 // assign RDF classes to this field
686 EntityHash::ConstIterator entity = entities.find(predicateChain);
688 if (entities.end() == entity) {
689 qctWarn(QString::fromLatin1("Cannot find entities for %3 property of %1 detail's %2 field").
690 arg(detail.definitionName(), field.name(), predicateChain.last().toString()));
693 for(; entity != entities.end() && entity.key() == predicateChain; ++entity) {
694 // create insert statement of this entity type
695 object = objectCache.insert(predicateChain, objectVariable);
696 insert(*object, rdf::type::iri(), **entity);
700 // create insert statement with the field's value
701 insert(axis.last(), pi->iri(), *object);
702 axis.append(m_explicitInserts.last().object());
704 // insert custom values for subTypes field
705 if (subTypeField && subTypeField->permitsCustomValues()) {
706 insertCustomValues(axis.last(), detail.definitionName(), *subTypeField,
707 detail.fieldValue(subTypeField->name()));
711 // find proper type of the value to insert
712 if (not field.isWithoutMapping()) {
713 const QUrl subTypePredicate = findSubTypePredicate(detail, field, subTypeField);
714 insert(axis.last(), subTypePredicate, QTrackerContactQueryBuilder::value(value));
717 // insert computed values
718 foreach(const PropertyInfoBase &pi, field.computedProperties()) {
719 QVariant computedValue;
721 if (pi.conversion()->makeValue(value, computedValue)) {
722 insert(axis.last(), pi.iri(), QTrackerContactQueryBuilder::value(computedValue));
726 // store custom values
727 if (field.permitsCustomValues()) {
728 insertCustomValues(axis.last(), detail.definitionName(), field, value);
732 UpdateBuilder::UpdateBuilder(const QTrackerContactSaveRequest *request,
733 const QContact &contact, const QDateTime ×tamp)
735 , m_schema(request->engine()->schema(contact.type()))
736 , m_classes(request->engine()->classes())
738 // figure out the contact's timestamps
739 QContactTimestamp timestampDetail(contact.detail<QContactTimestamp>());
741 m_accessed = timestampDetail.variantValue(QContactTimestamp__FieldAccessedTimestamp).toDateTime();
742 m_hadAccessed = not m_accessed.isNull();
744 if (m_accessed.isNull()) {
745 m_accessed = timestamp;
748 m_created = timestampDetail.created();
749 m_hadCreated = not m_created.isNull();
751 if (m_created.isNull()) {
752 m_created = timestamp;
755 m_lastModified = timestampDetail.lastModified();
756 m_hadLastModified = not m_lastModified.isNull();
758 if (m_lastModified.isNull() || 0 != contact.localId()) {
759 // only preserve the lastModified field for new contacts...
760 m_lastModified = timestamp;
763 // figure out the contact's GUID
764 QContactGuid guidDetail(contact.detail<QContactGuid>());
766 m_guid = guidDetail.guid();
767 m_hadGuid = not m_guid.isEmpty();
769 if (m_guid.isEmpty()) {
770 m_guid = qctUuidString();
773 // calculate local contact ID
774 m_contactLocalId = contact.localId();
776 if (0 == m_contactLocalId) {
777 m_contactLocalId = qHash(guid());
780 m_isSelfContact = (m_contactLocalId == request->engine()->selfContactId(0));
782 // collect details and update their URIs
783 foreach(const QContactDetail &detail, contact.details()) {
784 const QTrackerContactDetail *definition(m_schema.detail(detail.definitionName()));
786 if (0 == definition) {
787 if (m_schema.isSyntheticDetail(detail.definitionName())) {
788 continue; // don't even store synthesized details as nao:Property
792 m_mappings.append(DetailMapping(detail, definition));
795 foreach(DetailMapping detail, m_mappings) {
796 detail.updateDetailUri(contactLocalId(), m_mappings);
801 UpdateBuilder::insert(const RDFVariable &subject, const RDFVariable &predicate,
802 const RDFVariable &value, bool explicitValue)
805 m_explicitInserts += RDFStatement(subject, predicate, value);
807 RDFVariable restricted = subject.metaValue().iri();
808 RDFVariable property = restricted.optional().property(predicate);
810 m_implicitInserts += RDFStatement(restricted, predicate, value);
812 not restricted.variable(property).isBound();
817 UpdateBuilder::insertCustomDetail(RDFVariable &subject, const DetailMapping &detail)
819 const QString detailName = detail.definitionName();
820 const QVariantMap fields = detail.fieldValues();
822 RDFVariable detailProperty(makeUniqueName(detailName));
824 insert(subject, nao::hasProperty::iri(), detailProperty);
825 insert(detailProperty, rdf::type::iri(), nao::Property::iri());
826 insert(detailProperty, nao::propertyName::iri(), LiteralValue(detailName));
828 for(QVariantMap::ConstIterator i = fields.begin(); i != fields.end(); ++i) {
829 insertCustomValues(detailProperty, detailName, i.key(), i.value());
834 UpdateBuilder::collectInsertions(RDFUpdate &update)
836 RDFVariable affilination;
838 // Removing Role properties also removed the contact's PersonContact type,
839 // so we have to restore it.
840 RDFVariable contact = schema().makeContactIri(contactLocalId());
842 // Restore the resource type, we removed it for quick propertly deletion.
843 foreach(const QUrl &classIri, schema().contactClassIris()) {
844 insert(contact, rdf::type::iri(), classIri);
847 // Always update the local UID to support a-priori contacts like the self contact.
848 // Accept update errors if that property already exists with different value as
849 // this property is assumed to be immutable.
850 const LiteralValue contactLocalUID(QString::number(contactLocalId()));
851 insert(contact, nco::contactLocalUID::iri(), contactLocalUID);
853 // Collect inserts for each regular detail
854 foreach(const DetailMapping &detail, mappings()) {
855 // store custom details via nao:Property
856 if (detail.isCustomDetail()) {
857 insertCustomDetail(contact, detail);
861 // don't save synthesized details
862 if (detail.isSyntheticDetail()) {
866 const QVariantMap detailValues = detail.fieldValues();
867 const EntityHash entities = collectEntities(classes(), detail);
868 const bool isAffiliatedDetail = detail.isAffiliatedDetail();
869 const bool isPersonalDetail = detail.isPersonalDetail();
870 PredicateVariableHash objectCache;
872 foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
873 if (field.hasSubTypes()) {
874 // Subtypes are stored indirectly by assigning the proper resource class
875 // to the RDF resource representing this field, or by selecting the matching
876 // RDF property for storing the detail value.
880 const QVariantMap::ConstIterator fieldValue = detailValues.find(field.name());
884 if (fieldValue == detailValues.end() ||
885 not field.makeValue(*fieldValue, rdfValue)) {
886 // Could not compute the RDF value for this field.
890 if (isPersonalDetail) {
891 insertDetailField(contactLocalId(), contact, detail, field,
892 rdfValue, entities, objectCache);
895 if (isAffiliatedDetail) {
896 // create nco::Affiliation entity when needed
897 if (not affilination.metaIsDefinite()) {
898 affilination.metaAssign(makeAffiliationIri(contactLocalId()));
900 insert(affilination, rdf::type::iri(), nco::Affiliation::iri());
901 insert(contact, nco::hasAffiliation::iri(), affilination);
904 insertDetailField(contactLocalId(), affilination, detail, field,
905 rdfValue, entities, objectCache);
910 insert(contact, nie::contentLastModified::iri(), LiteralValue(lastModified()));
911 insert(contact, nie::contentCreated::iri(), LiteralValue(created()), hadCreated());
912 insert(contact, nco::contactUID::iri(), LiteralValue(guid()), hadGuid());
914 update.addInsertion(m_explicitInserts);
916 foreach(const RDFStatement &statement, m_implicitInserts) {
917 update.addInsertion(statement);
922 UpdateBuilder::deleteContactProperties(RDFUpdate &update) const
924 RDFVariable contact = schema().makeContactIri(contactLocalId());
926 RDFVariable domain(QString::fromLatin1("d"));
927 RDFVariable predicate(QString::fromLatin1("p"));
928 RDFVariable object(QString::fromLatin1("o"));
930 // Only remove properties of inherited classes in managed ontologies
931 domain.isMemberOf(iriVariableList(schema().inheritedClassIris()));
933 predicate.property<rdfs::domain>(domain);
934 predicate.notEqual(nco::contactLocalUID::iri());
936 // Remove all properties of Role, Contact and PersonContact domain,
937 // except for local id and globally unique id.
939 predicate.notEqual(nco::contactUID::iri());
942 // NB#177560 - skip removing nco::IMAddress for self Contact
943 if (isSelfContact()) {
944 predicate.notEqual(nco::hasIMAddress::iri());
947 update.addDeletion(contact, predicate, object);
949 // Remove timestamps when needed.
951 update.addDeletion(contact, nie::contentAccessed::iri());
955 update.addDeletion(contact, nie::contentCreated::iri());
958 update.addDeletion(contact, nie::contentLastModified::iri());
961 update.addDeletion(contact, nao::hasTag::iri());
965 UpdateBuilder::deleteCustomDetails(RDFUpdate &update) const
967 RDFVariable contact = schema().makeContactIri(contactLocalId());
968 RDFVariable customDetail(QString::fromLatin1("customDetail"));
969 RDFVariable customField(QString::fromLatin1("customField"));
971 update.addDeletion(RDFStatementList() <<
972 RDFStatement(customField, rdf::type::iri(), nao::Property::iri()) <<
973 RDFStatement(customDetail, rdf::type::iri(), nao::Property::iri()) <<
974 RDFStatement(customDetail, rdf::type::iri(), rdfs::Resource::iri()) <<
975 RDFStatement(contact, nao::hasProperty::iri(), customDetail));
977 contact.property<nao::hasProperty>(customDetail);
978 customDetail.property<nao::hasProperty>(customField);
982 deleteCustomValues(RDFUpdate &update, const RDFVariable &resource,
983 const QTrackerContactDetail &definition)
985 RDFVariableList customDetailFields;
987 foreach(const QTrackerContactDetailField &field, definition.fields()) {
988 if (field.permitsCustomValues()) {
989 customDetailFields += LiteralValue(field.name());
994 if (not customDetailFields.isEmpty()) {
995 RDFVariable customProperty = resource.property<nao::hasProperty>();
996 update.addDeletion(customProperty, rdf::type::iri(), nao::Property::iri());
997 customProperty.property<nao::propertyName>().isMemberOf(customDetailFields);
1002 UpdateBuilder::deleteAssociatedObjects(RDFUpdate &update) const
1004 RDFVariable contact = schema().makeContactIri(contactLocalId());
1006 // Remove all affiliation and organization properties in one go.
1007 // Initially we limited to nco:Role instead of rdf:Resource to only hit
1008 // "owned" properties, but this causes trouble with properties like
1009 // nco:fullname which for instance also implements dc:title.
1010 RDFVariable affiliation = makeAffiliationIri(contactLocalId());
1011 update.addDeletion(affiliation, rdf::type::iri(), rdfs::Resource::iri());
1013 RDFVariable organization = makeOrganizationIri(contactLocalId());
1014 update.addDeletion(organization, rdf::type::iri(), rdfs::Resource::iri());
1016 // Reset subtypes of associated entities
1017 foreach(const QTrackerContactDetail &definition, schema().details()) {
1018 const QTrackerContactDetailField *const subTypeField = definition.subTypeField();
1019 if (0 == subTypeField || not subTypeField->hasSubTypes()) {
1023 const QTrackerContactDetailField *const subjectField = definition.subjectField();
1025 if (0 == subjectField) {
1026 // There is no subject field for this detail.
1030 const PropertyInfoList &subjectPropertyChain = subjectField->propertyChain();
1032 if (subjectPropertyChain.count() < 2) {
1033 // There must be at least the property creating the association
1034 // and the property storing the value.
1038 const PropertyInfoBase &subjectProperty = subjectPropertyChain[1];
1039 const QTrackerContactSubject::Scheme scheme = subjectProperty.domainScheme();
1041 if (QTrackerContactSubject::isContentScheme(scheme)) {
1042 // delete associated object by its content IRI
1043 typedef QPair<QUrl, QUrl> IriPair;
1044 QList<IriPair> resourceIris;
1046 foreach(const DetailMapping &mapping, mappings()) {
1047 if (mapping.definitionName() != definition.name()) {
1051 const QVariant value = mapping.fieldValue(subjectField->name());
1053 if (value.isNull()) {
1057 resourceIris += qMakePair(makeIri(scheme, QVariantList() << value),
1058 subjectProperty.domainIri());
1061 foreach(const IriPair &iris, resourceIris) {
1062 deleteCustomValues(update, iris.first, definition);
1065 foreach(const IriPair &iris, resourceIris) {
1066 update.addDeletion(iris.first, rdf::type::iri(), iris.second);
1069 // no content IRI known, delete via association
1070 const PropertyInfoBase &associatingProperty = subjectPropertyChain[0];
1071 RDFVariable resource = associatingProperty.bindProperty(contact);
1073 deleteCustomValues(update, resource.deepCopy(), definition);
1074 update.addDeletion(resource, rdf::type::iri(), rdfs::Resource::iri());
1080 UpdateBuilder::collectDeletions(RDFUpdate &update) const
1082 deleteContactProperties(update);
1083 deleteCustomDetails(update);
1084 deleteAssociatedObjects(update);
1088 UpdateBuilder::makeIri(QTrackerContactSubject::Scheme scheme, const QVariantList &values) const
1090 return QTrackerContactSubject::makeIri(scheme, contactLocalId(), values);
1094 QTrackerContactSaveRequest::normalizeCurrentContact()
1096 QContact &contact = m_contacts[m_contactOffset];
1098 foreach(const QTrackerContactDetail &detail, engine()->schema(contact.type()).details()) {
1099 if (not detail.isUnique()) {
1103 QList<QContactDetail> contactDetails(contact.details(detail.name()));
1105 if (contactDetails.count() < 2) {
1109 qctWarn(QString::fromLatin1("Dropping odd details for contact %1: "
1110 "%2 detail must be unique").
1111 arg(m_contactOffset).arg(detail.name()));
1113 for(int i = 1; i < contactDetails.count(); ++i) {
1114 contact.removeDetail(&contactDetails[i]);
1120 makeAvatarCacheFileName(const QImage& image)
1122 // FIXME: H-Contacts needs to fix loading of images from URI
1123 // when not inside their own cache directory.
1125 const QDir avatarCacheDir = qctAvatarCacheDir();
1127 // Create avatar cache directory when needed.
1129 if (not avatarCacheDir.mkpath(QLatin1String("."))) {
1130 qctWarn(QString::fromLatin1("Cannot create avatar cache folder %1: %2").
1131 arg(avatarCacheDir.path(), strerror(errno)));
1135 // Create filename from cryptographic hash of the pixel data. Probability that two
1136 // different pictures on the same device have the same hash code, is significantly
1137 // smaller than probability that we mess up because of miscalculations caused by
1138 // electrostatics and flipping bits. That's why we can apply this cheap trick.
1140 const QByteArray pixels(reinterpret_cast<const char *>(image.constBits()), image.byteCount());
1141 const QString pixelHash = QCryptographicHash::hash(pixels, QCryptographicHash::Sha1).toHex();
1143 const QString fileName = QString::fromLatin1("%1.png").arg(pixelHash);
1144 return avatarCacheDir.absoluteFilePath(fileName);
1147 static QSet<QString>
1148 findAccountDetailUris(const QContact &contact)
1150 QSet<QString> onlineAccountUris;
1152 foreach(const QContactOnlineAccount &detail, contact.details<QContactOnlineAccount>()) {
1153 onlineAccountUris += detail.detailUri();
1156 return onlineAccountUris;
1160 isLinkedDetail(const QContactDetail &detail, const QSet<QString> &linkedDetailUris)
1162 foreach(const QString &uri, detail.linkedDetailUris()) {
1163 if (linkedDetailUris.contains(uri)) {
1172 QTrackerContactSaveRequest::normalizeAvatars()
1174 QContact &contact = m_contacts[m_contactOffset];
1175 const QSet<QString> accountDetailUris = findAccountDetailUris(contact);
1177 // Find user supplied thumbnail of the contact itself.
1178 QContactThumbnail defaultThumbnail;
1180 foreach(const QContactThumbnail &thumbnail, contact.details<QContactThumbnail>()) {
1181 if (not isLinkedDetail(thumbnail, accountDetailUris)) {
1182 defaultThumbnail = thumbnail;
1187 if (defaultThumbnail.isEmpty()) {
1191 // Find default avatar of the contact itself.
1192 QContactAvatar defaultAvatar;
1194 foreach(const QContactAvatar &avatar, contact.details<QContactAvatar>()) {
1195 if (not isLinkedDetail(avatar, accountDetailUris)) {
1196 defaultAvatar = avatar;
1201 // Get avatar thumbnail from detail and scale down when needed.
1202 const QSize avatarSize = QTrackerContactSettings().avatarSize();
1203 QImage thumbnailImage = defaultThumbnail.thumbnail();
1205 if (thumbnailImage.width() > avatarSize.width() ||
1206 thumbnailImage.height() > avatarSize.height()) {
1207 thumbnailImage = thumbnailImage.scaled(avatarSize, Qt::KeepAspectRatio);
1210 // Save the avatar image when neeed. The cached file name is based on
1211 // a cryptographic hash of the thumbnail's pixels. So if there already
1212 // exists a image file with the calculated name, there is an incredibly
1213 // high probability the existing file is equal to this current avatar.
1214 // We prefer to not write the file in this case for better performance
1215 // and for better lifetime of the storage medium.
1216 const QString avatarCacheFileName = makeAvatarCacheFileName(thumbnailImage);
1217 bool avatarWritten = true;
1219 if (avatarCacheFileName.isEmpty()) {
1220 avatarWritten = false; // failed to build file name
1221 } else if (not QFile::exists(avatarCacheFileName)) {
1222 QImageWriter writer(avatarCacheFileName);
1224 avatarWritten = writer.write(thumbnailImage);
1226 if (not avatarWritten) {
1227 qctWarn(QString::fromLatin1("Cannot save avatar thumbnail for contact %1: %2").
1228 arg(m_contactOffset).arg(writer.errorString()));
1232 if (avatarWritten) {
1233 // everything went find - update avatar detail and proceed
1234 defaultAvatar.setImageUrl(QUrl::fromLocalFile(avatarCacheFileName));
1235 contact.saveDetail(&defaultAvatar);
1236 } else if (not defaultAvatar.isEmpty()) {
1237 // failed to save avatar picture - remove avatar detail
1238 contact.removeDetail(&defaultAvatar);
1241 return avatarWritten;
1245 QTrackerContactSaveRequest::buildQuery(QContact &contact)
1247 UpdateBuilder builder(this, contact, timestamp());
1249 // Create local contact id when needed
1250 QContactId id = contact.id();
1251 id.setLocalId(builder.contactLocalId());
1252 id.setManagerUri(engine()->managerUri());
1255 // Build the update query
1258 builder.collectDeletions(update);
1259 builder.collectInsertions(update);
1264 ///////////////////////////////////////////////////////////////////////////////////////////////////
1267 QTrackerContactSaveRequest::saveContact()
1269 if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1271 << metaObject()->className() << m_stopWatch.elapsed()
1272 << ": contact" << m_contactOffset << "- updating";
1276 normalizeCurrentContact();
1278 // normalize and save avatar
1279 if (not normalizeAvatars()) {
1283 // build the update query and run it
1284 const RDFUpdate query = buildQuery(m_contacts[m_contactOffset]);
1285 const QString queryString = ::BackEnds::Tracker::tracker()->rawQueryString(query);
1287 if (engine()->hasDebugFlag(QContactTrackerEngine::ShowUpdates)) {
1288 qDebug() << queryString;
1291 QDBusPendingCall updateCall = QctTracker::Resources::
1292 batchSparqlUpdate(m_trackerInstance, queryString,
1293 engine()->trackerTimeout());
1295 QTrackerContactSaveRequestTask *pendingUpdate =
1296 new QTrackerContactSaveRequestTask(updateCall, m_contactOffset, this);
1298 connect(pendingUpdate, SIGNAL(failure(int,QString)), SLOT(failure(int,QString)));
1299 connect(pendingUpdate, SIGNAL(success(int)), SLOT(success(int)));
1300 m_pendingTasks.insert(m_contactOffset, pendingUpdate);
1302 if (++m_batchSize >= engine()->batchSize()) {
1308 QTrackerContactSaveRequest::proceed()
1310 // Too many updates are running still, let's abort.
1311 // We'll get called again when next pending update is commited.
1312 if (m_pendingTasks.count() >= engine()->concurrencyLevel() / 2 &&
1313 m_pendingTasks.count() > 0) {
1317 while (m_contactOffset < m_contacts.count()) {
1318 // Now we have queued too many updates, let's abort.
1319 // We'll get called again when next pending update is commited.
1320 if (m_pendingTasks.count() >= engine()->concurrencyLevel()) {
1328 if (hasPendingUpdates()) {
1329 commit(); // We left the saveContact() loop, and now must commit the queued updates.
1333 // We left the saveContact() loop, and don't have any pending updates or
1334 // pending commits. This shows the save request was finished. Let's report this.
1335 if (not hasPendingCommit()) {
1336 if (m_errorMap.isEmpty()) {
1337 m_effectiveError = QContactManager::NoError;
1340 emitResult(m_effectiveError);
1345 QTrackerContactSaveRequest::commit()
1347 // Don't do anything if another commit is running still.
1348 if (hasPendingCommit()) {
1352 // Dump some debug information when requested.
1353 if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1355 << metaObject()->className() << m_stopWatch.elapsed()
1356 << "- commiting" << m_batchSize << "pending updates";
1359 // Tell tracker to commit queued updates.
1360 QDBusPendingCall commitCall = QctTracker::Resources::
1361 batchCommit(m_trackerInstance, engine()->trackerTimeout());
1363 m_commit.reset(new QDBusPendingCallWatcher(commitCall));
1365 connect(m_commit.data(),
1366 SIGNAL(finished(QDBusPendingCallWatcher*)),
1367 SLOT(commitFinished()));
1373 QTrackerContactSaveRequest::commitFinished()
1375 if (m_commit->isError()) {
1376 // Seems we don't have to do anything for for this error except for printing
1377 // it to console: We won't get a success signal for the failed update, and
1378 // therefore the failed contact should stay in m_errorMap[] causing proper
1380 qctWarn(QString::fromLatin1("Commit failed: %1").arg(m_commit->error().message()));
1384 m_commit.reset(); // Mark this commit as done.
1385 proceed(); // Check if other updates are pending, or if more contacts must be written.
1389 QTrackerContactSaveRequest::success(int offset)
1391 if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1393 << metaObject()->className() << m_stopWatch.elapsed()
1394 << ": contact" << offset << " - update succeded";
1397 delete m_pendingTasks.take(offset); // Mark this update as done.
1398 m_errorMap.remove(offset); // Mark this update's contact as successfully written.
1399 proceed(); // Check if other updates are pending, or if more contacts must be written.
1403 QTrackerContactSaveRequest::failure(int offset, QString const &message)
1405 qctWarn(QString::fromLatin1("Failed to update contact %1: %2").arg(offset).arg(message));
1407 delete m_pendingTasks.take(offset); // Mark this update as done.
1408 proceed(); // Check if other updates are pending, or if more contacts must be written.
1410 // Notice: We don't remove the update's contact from m_errorMap[]
1411 // which results in proper error reporting.
1414 ///////////////////////////////////////////////////////////////////////////////////////////////////
1417 QTrackerContactSaveRequest::hasPendingUpdates() const
1419 return not m_pendingTasks.isEmpty();
1423 QTrackerContactSaveRequest::hasPendingCommit() const
1425 return not m_commit.isNull();
1428 ///////////////////////////////////////////////////////////////////////////////////////////////////
1431 QTrackerContactSaveRequest::start()
1433 if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1435 << metaObject()->className() << 0
1436 << ": number of contacts:" << m_request->contacts().count();
1439 m_stopWatch.start();
1441 // prepare error map with default values
1442 for(int i = 0; i < m_contacts.count(); ++i) {
1443 m_errorMap.insert(i, QContactManager::UnspecifiedError);
1446 // find tracker instance
1447 m_trackerInstance = QctTracker::instance();
1449 if (m_trackerInstance.isEmpty()) {
1450 qctWarn(QString::fromLatin1("tracker service not found: %1").arg(QctTracker::lastError));
1455 m_contactOffset = 0;
1456 QTimer::singleShot(0, this, SLOT(proceed()));
1462 QTrackerContactSaveRequest::emitResult(QContactManager::Error error)
1464 if (engine()->hasDebugFlag(QContactTrackerEngine::ShowTiming)) {
1466 << metaObject()->className() << m_stopWatch.elapsed()
1467 << ": reporting result" << error;
1470 engine()->updateContactSaveRequest(m_request, m_contacts, error, m_errorMap,
1471 QContactAbstractRequest::FinishedState);
1474 ///////////////////////////////////////////////////////////////////////////////////////////////////
1476 #include "contactsaverequest.moc"