Fixes: NB#167342
[qtcontacts-tracker:qtcontacts-tracker.git] / src / engine / contactsaverequest.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2009 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
44 #include <dao/contactslive.h>
45 #include <dao/subject.h>
46 #include <dao/settings.h>
47 #include <dao/trackerchangelistener.h>
48
49 #include <QtTracker/Tracker>
50 using namespace SopranoLive;
51
52
53 // TODO better error handling when saving
54 QTrackerContactSaveRequest::QTrackerContactSaveRequest(QContactAbstractRequest* req,
55                                                        QContactTrackerEngine *engine)
56 : QObject(engine), mRequest(0), mEngine(engine), errorCount(0)
57 {
58     Q_ASSERT(req);
59     Q_ASSERT(req->type() == QContactAbstractRequest::ContactSaveRequest);
60     Q_ASSERT(engine);
61
62     mRequest = qobject_cast<QContactSaveRequest*>(req);
63
64     if (!mRequest) {
65         QContactManagerEngine::updateRequestState(mRequest, QContactAbstractRequest::FinishedState);
66         return;
67     }
68
69     QList<QContact> contacts = mRequest->contacts();
70
71     if(contacts.isEmpty()) {
72         QMap<int, QContactManager::Error> errors; 
73         errors[0] = QContactManager::BadArgumentError;
74         QContactManagerEngine::updateContactSaveRequest(mRequest, contacts, QContactManager::BadArgumentError, errors, mRequest->state());
75         return;
76     }
77
78     QContactManagerEngine::updateRequestState(mRequest, QContactAbstractRequest::ActiveState);
79
80     TrackerChangeListener *changeListener = new TrackerChangeListener(engine, this);
81     connect(changeListener, SIGNAL(contactsChanged(const QList<QContactLocalId> &)),SLOT(onTrackerSignal(const QList<QContactLocalId> &)));
82     connect(changeListener, SIGNAL(contactsAdded(const QList<QContactLocalId> &)),SLOT(onTrackerSignal(const QList<QContactLocalId> &)));
83
84     // Save contacts with batch size
85     /// @todo where to get reasonable batch size
86     int batchSize = 100;
87     for (int i = 0; i < contacts.size(); i+=batchSize) {
88         saveContacts(contacts.mid(i, batchSize));
89     }
90 }
91
92 void QTrackerContactSaveRequest::onTrackerSignal(const QList<QContactLocalId> &ids)
93 {
94     computeProgress(ids);
95 }
96
97 void QTrackerContactSaveRequest::computeProgress(const QList<QContactLocalId> &addedIds)
98 {
99     Q_ASSERT(mRequest->type() == QContactAbstractRequest::ContactSaveRequest);
100     
101     foreach (QContactLocalId id, addedIds) {
102         pendingContactIds.remove(id);
103         // since if was OK, remove entry for error
104         errorsOfContactsFinished.remove(id2Index[id]);
105     }
106
107     if (pendingContactIds.count() == 0) {
108         // compute master error - part of qtcontacts api
109         QContactManager::Error error = QContactManager::NoError;
110         
111         foreach(QContactManager::Error err, errorsOfContactsFinished.values()) {
112             if( QContactManager::NoError != err )
113             {
114                 error = err;
115                 break;
116             }
117         }
118
119         QContactManagerEngine::updateContactSaveRequest(mRequest, contactsFinished, error, errorsOfContactsFinished, QContactAbstractRequest::FinishedState);
120     }
121 }
122
123 void addTag(RDFServicePtr service, Live<nco::PersonContact> ncoContact, const QString &tag)
124 {
125     Live<nao::Tag> ncotag = service->liveNode(QUrl(QString::fromLatin1("tag:%1").arg(tag)));
126     ncotag->setPrefLabel(tag);
127     ncoContact->removeNaoHasTag(ncotag);
128     ncoContact->addNaoHasTag(ncotag);
129 }
130
131 void QTrackerContactSaveRequest::saveContacts(const QList<QContact> &contacts)
132 {
133     QContactManagerEngine *engine = qobject_cast<QContactManagerEngine *> (parent());
134     Q_ASSERT(engine);
135
136     QTrackerContactSettings settings;
137     QTrackerContactsLive cLive;
138     RDFServicePtr service = cLive.service();
139
140     foreach(QContact contact, contacts) {
141
142         /* Full validation (including invalid detail) is disabled because it blocks saving contacts parsed
143          * from vcards. Checking only against uniqueness requirement.
144          * TODO take validation into use once opaque (custom) details are clarified
145          */
146 /*
147         QContactManager::Error error;
148         engine->validateContact(contact, error);
149         if(error == QContactManager::AlreadyExistsError){ // if detail is not unique and needs to be
150             contactsFinished << contact;
151             errorsOfContactsFinished[errorCount++] =  error;
152             computeProgress(QList<QContactLocalId>());
153             continue;
154         }
155 */
156         Live<nco::PersonContact> ncoContact;
157         bool newContact = false;
158
159         if(contact.localId() == 0) {
160             // Save new contact. compute ID
161             // what if both processes read in the same time and write at the same time, no increment
162             QContactLocalId m_lastUsedId = settings.lastLocalId();
163             settings.setLastLocalId(++m_lastUsedId);
164
165             ncoContact = service->liveNode(makeContactIri(m_lastUsedId));
166             QContactId id;
167             id.setLocalId(m_lastUsedId);
168             id.setManagerUri(engine->managerUri());
169             contact.setId(id);
170             ncoContact->setContactLocalUID(m_lastUsedId);
171             ncoContact->setContactUID(QString::number(m_lastUsedId));
172             ncoContact->setContentCreated(QDateTime::currentDateTime());
173             newContact = true;
174         }  else {
175             ncoContact = service->liveNode(makeContactIri(contact.localId()));
176             /// @note Following needed in case we save new contact with given localId
177             ncoContact->setContactLocalUID(contact.localId());
178             ncoContact->setContactUID(QString::number(contact.localId()));
179             ncoContact->setContentLastModified(QDateTime::currentDateTime());
180         }
181         pendingContactIds.insert(contact.localId());
182
183         // if there are work related details, need to be saved to Affiliation.
184         if( QTrackerContactSaveRequest::contactHasWorkRelatedDetails(contact)) {
185             addAffiliation(service, contact.localId());
186         }
187
188         // Add a special tag for contact added from addressbook, not from fb, telepathy etc.
189         // this is important when returning contacts to sync team
190         RDFVariable rdfContact = RDFVariable::fromType<nco::PersonContact>();
191         rdfContact.property<nco::contactLocalUID>() = LiteralValue(contact.localId());
192
193         addTag(service, ncoContact, "addressbook");
194
195         // name & nickname - different way from other details
196         cLive.setLiveContact(ncoContact);
197         cLive.setQContact(contact);
198         if( !contact.detail<QContactName>().isEmpty() || !contact.detail<QContactNickname>().isEmpty() ) {
199             cLive.saveName();
200         }
201
202         saveContactDetails( service, ncoContact, contact, newContact);
203
204         contactsFinished << contact;
205         id2Index[contact.localId()] = errorCount;
206         // we fill error here - once response come that everything is OK, remove entry for this contact
207         errorsOfContactsFinished[errorCount++] =  QContactManager::BadArgumentError;
208     }
209     // remember to commit the transaction, otherwise all changes will be rolled back.
210     cLive.commit();
211 }
212
213
214 QTrackerContactSaveRequest::~QTrackerContactSaveRequest()
215 {
216     // TODO Auto-generated destructor stub
217 }
218
219 /*!
220 * Saving has to go in such way that all names are saved at once, all phone numbers together
221 * filled to rdfupdate query etc.
222 * This method goes through the contact and collect which contact detail definitions are there
223 */
224 QStringList QTrackerContactSaveRequest::detailsDefinitionsInContact(const QContact &c)
225 {
226     QStringList definitions;
227     foreach(const QContactDetail& det, c.details())
228         {
229             definitions << det.definitionName();
230         }
231     definitions.removeDuplicates();
232     return definitions;
233 }
234
235 //! Just moving this code out of saveContact to make it shorter
236 bool QTrackerContactSaveRequest::contactHasWorkRelatedDetails(const QContact &c)
237 {
238     foreach(const QContactDetail& det, c.details())
239     {
240         if( det.contexts().contains(QContactDetail::ContextWork))
241            return true;
242     }
243     return false;
244 }
245
246 // create nco::Affiliation if there is not one already in tracker
247 void QTrackerContactSaveRequest::addAffiliation(RDFServicePtr service, QContactLocalId contactId)
248 {
249     Live<nco::PersonContact> ncoContact = service->liveNode(makeContactIri(contactId));
250     Live<nco::Affiliation> ncoAffiliation = service->liveNode(makeAffiliationIri(contactId));
251     ncoContact->setHasAffiliation(ncoAffiliation);
252 }
253
254 void QTrackerContactSaveRequest::saveContactDetails( RDFServicePtr service,
255                                                 Live<nco::PersonContact>& ncoContact,
256                                                 const QContact& contact,
257                                                 bool newContact)
258 {
259     QStringList detailDefinitionsToSave = detailsDefinitionsInContact(contact);
260
261     // all the rest might need to save to PersonContact and to Affiliation contact
262     RDFVariable rdfPerson = RDFVariable::fromType<nco::PersonContact>();
263     rdfPerson.property<nco::contactUID>() = LiteralValue(QString::number(contact.localId()));
264
265     // first we remove all data related to the existing contact
266     if(not newContact) {
267         // Delete all existing phone numbers - office and home
268         deletePhoneNumbers(service, rdfPerson);
269         deleteEmailAddresses(service, rdfPerson);
270         deleteUrls(service, rdfPerson);
271     }
272
273     foreach(QString definition, detailDefinitionsToSave)
274     {
275         QList<QContactDetail> details = contact.details(definition);
276         Q_ASSERT(!details.isEmpty());
277
278         RDFVariable rdfAffiliation;
279         RDFVariable rdfPerson1;
280         rdfPerson1.property<nco::hasAffiliation>() = rdfAffiliation;
281         rdfPerson1.property<nco::contactUID>() = LiteralValue(QString::number(contact.localId()));
282
283         QList<QContactDetail> workDetails;
284         QList<QContactDetail> homeDetails;
285         foreach(const QContactDetail& det, details) {
286             // details can be for both contexts, so check for both seperately
287             if( det.contexts().contains(QContactDetail::ContextWork) ) {
288                 workDetails << det;
289             }
290             if( det.contexts().contains(QContactDetail::ContextHome)) {
291                 homeDetails << det;
292             }
293             if( !det.contexts().contains(QContactDetail::ContextHome)
294                 && !det.contexts().contains(QContactDetail::ContextWork)) {
295                 homeDetails << det;
296             }
297         }
298
299         // Save details
300         if(definition == QContactPhoneNumber::DefinitionName) {
301             if (!homeDetails.isEmpty()) {
302                 savePhoneNumbers(service, rdfPerson, homeDetails, newContact);
303             }
304             if( !workDetails.isEmpty()) {
305                 savePhoneNumbers(service, rdfAffiliation, workDetails, newContact);
306             }
307         }
308         else if(definition == QContactEmailAddress::DefinitionName) {
309             if (!homeDetails.isEmpty()) {
310                 saveEmails(service, rdfPerson, homeDetails, newContact);
311             }
312             if( !workDetails.isEmpty()) {
313                 saveEmails(service, rdfAffiliation, workDetails, newContact);
314             }
315         }
316         else if(definition == QContactAddress::DefinitionName) {
317             saveAddresses(service, rdfPerson, homeDetails, newContact);
318             saveAddresses(service, rdfAffiliation, workDetails, newContact);
319         }
320         else if(definition == QContactUrl::DefinitionName) {
321             saveUrls(service, rdfPerson, homeDetails, newContact);
322             saveUrls(service, rdfAffiliation, workDetails, newContact);
323         }
324         else {
325             // TODO refactor (bug: editing photo doesn't work)
326             foreach(const QContactDetail &det, details )
327             {
328                 definition = det.definitionName();
329                 if(definition == QContactAvatar::DefinitionName) {
330                     QUrl avatar = det.value(QContactAvatar::FieldImageUrl);
331                     Live<nie::DataObject> fdo = service->liveNode( avatar );
332                     ncoContact->setPhoto(fdo);
333                 }
334                 if(definition == QContactBirthday::DefinitionName) {
335                     ncoContact->setBirthDate(QDateTime(det.variantValue(QContactBirthday::FieldBirthday).toDate(), QTime(), Qt::UTC));
336                 }
337             } // end foreach detail
338         }
339     }
340 }
341
342 // Remove all existing references to phone numbers from the contact so that edits are
343 // reflected to Tracker correctly.
344 // Delete the references to phone numbers - not the numbers themselves as they remain in tracker
345 // with their canonical URI form - might be linked to history.
346 void QTrackerContactSaveRequest::deletePhoneNumbers(RDFServicePtr service, const RDFVariable& rdfContactIn)
347 {
348     {
349         RDFUpdate up;
350         RDFVariable rdfContact = rdfContactIn.deepCopy();
351         up.addDeletion(rdfContact, nco::hasPhoneNumber::iri(), rdfContact.property<nco::hasPhoneNumber>());
352         service->executeQuery(up);
353     }
354
355     // affiliation
356     {
357         RDFUpdate up;
358         RDFVariable rdfContact = rdfContactIn.deepCopy().property<nco::hasAffiliation>();
359         up.addDeletion(rdfContact, nco::hasPhoneNumber::iri(), rdfContact.property<nco::hasPhoneNumber>());
360         service->executeQuery(up);
361     }
362 }
363
364 void QTrackerContactSaveRequest::deleteEmailAddresses(RDFServicePtr service, const RDFVariable& rdfContactIn)
365 {
366     {
367         RDFUpdate up;
368         RDFVariable rdfContact = rdfContactIn.deepCopy();
369         up.addDeletion(rdfContact, nco::hasEmailAddress::iri(), rdfContact.property<nco::hasEmailAddress>());
370         service->executeQuery(up);
371     }
372
373     // affiliation
374     {
375         RDFUpdate up;
376         RDFVariable rdfContact = rdfContactIn.deepCopy().property<nco::hasAffiliation>();
377         up.addDeletion(rdfContact, nco::hasEmailAddress::iri(), rdfContact.property<nco::hasEmailAddress>());
378         service->executeQuery(up);
379     }
380 }
381
382 void QTrackerContactSaveRequest::deleteUrls(RDFServicePtr service, const RDFVariable& rdfContactIn)
383 {
384
385     {
386        /* RDFUpdate up;
387         RDFVariable rdfContact = rdfContactIn.deepCopy();
388         up.addDeletion(rdfContact, nco::url::iri(), rdfContact.property<nco::url>());
389         up.addDeletion(rdfContact, nco::websiteUrl::iri(), rdfContact.property<nco::websiteUrl>());
390         service->executeQuery(up);*/
391         RDFUpdate update;
392         RDFVariable rdfContact = rdfContactIn.deepCopy();
393         // first part - deleting previous before adding new again is to be removed
394         update.addDeletion(RDFVariableStatement(rdfContact.child(), nco::url::iri(), RDFVariable()));
395         update.addDeletion(RDFVariableStatement(rdfContact.child(), nco::websiteUrl::iri(), RDFVariable()));
396         service->executeQuery(update);
397     }
398
399     // affiliation
400     {
401         /*RDFUpdate up;
402         RDFVariable rdfContact = rdfContactIn.deepCopy().property<nco::hasAffiliation>();
403         up.addDeletion(rdfContact, nco::url::iri(), rdfContact.property<nco::url>());
404         up.addDeletion(rdfContact, nco::websiteUrl::iri(), rdfContact.property<nco::websiteUrl>());
405         service->executeQuery(up);*/
406         RDFUpdate update;
407         RDFVariable rdfContact = rdfContactIn.deepCopy();
408         // first part - deleting previous before adding new again is to be removed
409         update.addDeletion(RDFVariableStatement(rdfContact.child(), nco::url::iri(), RDFVariable()));
410         update.addDeletion(RDFVariableStatement(rdfContact.child(), nco::websiteUrl::iri(), RDFVariable()));
411         service->executeQuery(update);
412     }
413 }
414
415 // Strip of the non-numbers from 'number' and return N rightmost numbers as QString,
416 // N being defined on QSetting Nokia/Contacts/numberMatchLength
417 QString parseLocalNumber(QString number)
418 {
419     QSettings contactsSettings(QSettings::IniFormat, QSettings::UserScope,
420                                "Nokia", "Contacts");
421     int length = contactsSettings.value("numberMatchLength", "7").toInt();
422     //remove all non-numbers
423     QString phoneNumber(number.remove(QRegExp("[\\D]")));
424     QString localPhone = phoneNumber.right(length);
425     return localPhone;
426 }
427
428 /*!
429  * write all phone numbers on one query to tracker
430  */
431 void QTrackerContactSaveRequest::savePhoneNumbers(RDFServicePtr service, RDFVariable &var, const QList<QContactDetail> &details, bool newContact )
432 {
433     RDFUpdate up;
434     RDFVariable varForInsert = var.deepCopy();
435     foreach(const QContactDetail& det, details)
436     {
437         QString value(normalizePhoneNumber(det.value(QContactPhoneNumber::FieldNumber)));
438         const QUrl newPhone(makePhoneNumberIri(value, false));
439
440         // Temporary, because affiliation is still used - to be refactored next week to use Live nodes
441         Live<nco::PhoneNumber> ncoPhone = service->liveNode(newPhone);
442
443         if(not newContact) {
444             ncoPhone->remove();
445         }
446
447         QStringList subtypes = det.value<QStringList>(QContactPhoneNumber::FieldSubTypes);
448
449         if( subtypes.contains(QContactPhoneNumber::SubTypeMobile))
450             up.addInsertion(newPhone, rdf::type::iri(), nco::CellPhoneNumber::iri());
451         else if( subtypes.contains(QContactPhoneNumber::SubTypeCar))
452             up.addInsertion(newPhone, rdf::type::iri(), nco::CarPhoneNumber::iri());
453         else if( subtypes.contains(QContactPhoneNumber::SubTypeBulletinBoardSystem))
454             up.addInsertion(newPhone, rdf::type::iri(), nco::BbsNumber::iri());
455         else if( subtypes.contains(QContactPhoneNumber::SubTypeFax))
456             up.addInsertion(newPhone, rdf::type::iri(), nco::FaxNumber::iri());
457         else if( subtypes.contains(QContactPhoneNumber::SubTypeModem))
458             up.addInsertion(newPhone, rdf::type::iri(), nco::ModemNumber::iri());
459         else if( subtypes.contains(QContactPhoneNumber::SubTypePager))
460             up.addInsertion(newPhone, rdf::type::iri(), nco::PagerNumber::iri());
461         else if( subtypes.contains(QContactPhoneNumber::SubTypeMessagingCapable))
462             up.addInsertion(newPhone, rdf::type::iri(), nco::MessagingNumber::iri());
463         else
464             up.addInsertion(newPhone, rdf::type::iri(), nco::VoicePhoneNumber::iri());
465
466         up.addInsertion(newPhone, nco::phoneNumber::iri(), LiteralValue(value));
467         up.addInsertion(newPhone, maemo::localPhoneNumber::iri(), LiteralValue(parseLocalNumber(value)));
468         up.addInsertion(varForInsert, nco::hasPhoneNumber::iri(), newPhone);
469     }
470     service->executeQuery(up);
471 }
472
473 /*!
474  * write all phone numbers on one query to tracker
475  * TODO this is temporary code for creating new, saving contacts need to handle only what was
476  * changed.
477  */
478 void QTrackerContactSaveRequest::saveEmails(RDFServicePtr service, RDFVariable &var, const QList<QContactDetail> &details, bool newContact )
479 {
480     RDFUpdate up;
481     RDFVariable varForInsert = var.deepCopy();
482
483     foreach(const QContactDetail& det, details)
484     {
485         QString value = det.value(QContactEmailAddress::FieldEmailAddress);
486         // Temporary, because affiliation is still used - to be refactored next week to use only Live nodes
487         QUrl newEmail = makeEmailAddressIri(value);
488         Live<nco::EmailAddress> ncoEmail = service->liveNode(newEmail);
489         /*if(not newContact) {
490             ncoEmail->remove();
491         }*/
492         up.addInsertion(newEmail, rdf::type::iri(), nco::EmailAddress::iri());
493         up.addInsertion(newEmail, nco::emailAddress::iri(), LiteralValue(value));
494         up.addInsertion(RDFVariableStatement(varForInsert, nco::hasEmailAddress::iri(), newEmail));
495     }
496     service->executeQuery(up);
497 }
498
499 /*!
500  * write all Urls
501  * TODO this is temporary code for creating new, saving contacts need to handle only what was
502  * changed.
503  */
504 void QTrackerContactSaveRequest::saveUrls(RDFServicePtr service, RDFVariable &rdfContact, const QList<QContactDetail> &details, bool newContact )
505 {
506     RDFUpdate up;
507     RDFVariable varForInsert = rdfContact.deepCopy();
508
509     if(not newContact) {
510         RDFUpdate update;
511         // first part - deleting previous before adding new again is to be removed
512         update.addDeletion(RDFVariableStatement(rdfContact.child(), nco::url::iri(), RDFVariable()));
513         update.addDeletion(RDFVariableStatement(rdfContact.child(), nco::websiteUrl::iri(), RDFVariable()));
514         update.addDeletion(RDFVariableStatement(rdfContact, nco::url::iri(), RDFVariable()));
515         update.addDeletion(RDFVariableStatement(rdfContact, nco::websiteUrl::iri(), RDFVariable()));
516         service->executeQuery(update);
517     }
518
519     // second part, write all urls
520     foreach(const QContactDetail& det, details)
521     {
522         QUrl newUrl(det.value(QContactUrl::FieldUrl));//::tracker()->createLiveNode().uri();
523         if(det.value(QContactUrl::FieldSubType) == QContactUrl::SubTypeFavourite)
524         {
525             up.addInsertion(varForInsert, nco::url::iri(), newUrl);
526         }
527         else // if not favourite, then homepage. don't support other
528         {
529             up.addInsertion(varForInsert, nco::websiteUrl::iri(), newUrl); // add it to contact
530         }
531     }
532     service->executeQuery(up);
533 }
534
535 /*!
536  * write all phone numbers on one query to tracker
537  * TODO this is temporary code for creating new, saving contacts need to handle only what was
538  * changed.
539  */
540 void QTrackerContactSaveRequest::saveAddresses(RDFServicePtr service, RDFVariable &var, const QList<QContactDetail> &details, bool newContact )
541 {
542     RDFUpdate up;
543     RDFVariable varForInsert = var.deepCopy();
544
545     if(not newContact) {
546         up.addDeletion(RDFVariableStatement(var.child(), nco::hasPostalAddress::iri(), RDFVariable()));
547     }
548     foreach(const QContactDetail& det, details)
549     {
550         QUrl newPostalAddress = ::tracker()->createLiveNode().uri();
551         // TODO     nco:DomesticDeliveryAddress, nco:InternationalDeliveryAddress, nco:ParcelDeliveryAddress
552         up.addInsertion(newPostalAddress, rdf::type::iri(), nco::PostalAddress::iri());
553         up.addInsertion(newPostalAddress, nco::streetAddress::iri(), LiteralValue(det.value(QContactAddress::FieldStreet)));
554         up.addInsertion(newPostalAddress, nco::locality::iri(), LiteralValue(det.value(QContactAddress::FieldLocality)));
555         up.addInsertion(newPostalAddress, nco::country::iri(), LiteralValue(det.value(QContactAddress::FieldCountry)));
556         up.addInsertion(newPostalAddress, nco::postalcode::iri(), LiteralValue(det.value(QContactAddress::FieldPostcode)));
557         up.addInsertion(newPostalAddress, nco::region::iri(), LiteralValue(det.value(QContactAddress::FieldRegion)));
558         up.addInsertion(newPostalAddress, nco::pobox::iri(), LiteralValue(det.value(QContactAddress::FieldPostOfficeBox)));
559         up.addInsertion(RDFVariableStatement(varForInsert, nco::hasPostalAddress::iri(), newPostalAddress));
560     }
561     service->executeQuery(up);
562 }