Fixes: NB#167342
[qtcontacts-tracker:qtcontacts-tracker.git] / src / engine / contactfetchrequest.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 "contactfetchrequest.h"
43
44 #include <dao/settings.h>
45 #include <qtcontacts.h>
46
47 #include <QtTracker/ontologies/nie.h>
48 #include <QtTracker/ontologies/nco.h>
49
50 using namespace SopranoLive;
51
52 class ConversionLookup: public QHash<QString,QContactPresence::PresenceState>
53 {
54 public:
55     ConversionLookup& operator<<(const QPair<QString, QContactPresence::PresenceState> &conversion)
56     {
57         this->insert(conversion.first, conversion.second);
58         return *this;
59     }
60 };
61
62 const QString FieldQContactLocalId("QContactLocalId");
63 const QString FieldAccountPath("AccountPath");
64 const ConversionLookup presenceConversion(ConversionLookup()
65             <<QPair<QString, QContactPresence::PresenceState>("presence-status-offline", QContactPresence::PresenceOffline)
66             <<QPair<QString, QContactPresence::PresenceState>("presence-status-available", QContactPresence::PresenceAvailable)
67             <<QPair<QString, QContactPresence::PresenceState>("presence-status-away", QContactPresence::PresenceAway)
68             <<QPair<QString, QContactPresence::PresenceState>("presence-status-extended-away", QContactPresence::PresenceExtendedAway)
69             <<QPair<QString, QContactPresence::PresenceState>("presence-status-busy", QContactPresence::PresenceBusy)
70             <<QPair<QString, QContactPresence::PresenceState>("presence-status-unknown", QContactPresence::PresenceUnknown)
71             <<QPair<QString, QContactPresence::PresenceState>("presence-status-hidden", QContactPresence::PresenceHidden)
72             <<QPair<QString, QContactPresence::PresenceState>("presence-status-dnd", QContactPresence::PresenceBusy)
73 );
74
75 /*!
76  * \fn matchPhoneNumber()
77  * \brief apply \a filter to rdf graph defined by \a variable
78  * \a variable is defining some set of nco:PersonContacts. Here, filter is applied before calling modelQuery
79  * (::tracker()->modelQuery() is then fetching data defined by this rdf graph)
80  */
81 QContactManager::Error matchPhoneNumber(RDFVariable &variable, QContactDetailFilter &filter)
82 {
83     if (QContactPhoneNumber::FieldNumber != filter.detailFieldName()) {
84         return QContactManager::NotSupportedError;
85     }
86     RDFVariable officeContact;
87     RDFVariable homeContact;
88
89     RDFVariable rdfPhoneNumber;
90     rdfPhoneNumber = homeContact.property<nco::hasPhoneNumber>();
91
92     RDFVariable rdfOfficePhoneNumber;
93     rdfOfficePhoneNumber = officeContact.property<nco::hasAffiliation>().property<nco::hasPhoneNumber>();
94
95     QString filterValue = filter.value().toString();
96     if (filter.matchFlags() == QContactFilter::MatchEndsWith)
97     {
98         int matchDigitCount = QTrackerContactSettings().localPhoneNumberLength();
99         filterValue = filterValue.right(matchDigitCount);
100         rdfPhoneNumber.property<nco::phoneNumber>().hasSuffix(filterValue);
101         rdfOfficePhoneNumber.property<nco::phoneNumber>().hasSuffix(filterValue);
102     }
103     else if (filter.matchFlags() == QContactFilter::MatchPhoneNumber)
104     {
105         int matchDigitCount = QTrackerContactSettings().localPhoneNumberLength();
106         filterValue = filterValue.right(matchDigitCount);
107         qDebug() << "match phonenumber with:" << matchDigitCount << ":" << filterValue;
108         rdfPhoneNumber.property<maemo::localPhoneNumber>() =  LiteralValue(filterValue);
109         rdfOfficePhoneNumber.property<maemo::localPhoneNumber>() = LiteralValue(filterValue);
110     }
111     else
112     {   // default to exact match
113         rdfOfficePhoneNumber.property<nco::phoneNumber>().matchesRegexp(filterValue);
114         rdfPhoneNumber.property<nco::phoneNumber>().matchesRegexp(filterValue);
115     }
116     // This is the key part, including both contacts and affiliations
117     variable.isMemberOf(RDFVariableList()<<homeContact);// TODO use union if available in qttracker (planned for w10)
118     return QContactManager::NoError;
119 }
120
121 /*!
122  * \fn matchOnlineAccount()
123  * \brief apply \a filter to rdf graph defined by \a variable
124  * To define RDFquery graph for this one is tricky:
125  * need to find IMAccount -> hasIMContact -> IMAddress - the same IMAddress as contact \a variable
126  * has as PersonContact -> hasIMAddress -> IMAddress
127  */
128 QContactManager::Error matchOnlineAccount(RDFVariable &variable, QContactDetailFilter &filter)
129 {
130     if ((filter.matchFlags() & QContactFilter::MatchExactly) == QContactFilter::MatchExactly)
131     {
132         // \a variable PersonContact -> hasIMAddress -> imaddress
133         RDFVariable imaddress = variable.property<nco::hasIMAddress>();
134         if (filter.detailFieldName() == "Account" || filter.detailFieldName() == QContactOnlineAccount::FieldAccountUri) {
135             imaddress.property<nco::imID> ().isMemberOf(QStringList() << filter.value().toString());
136         }
137         else if (filter.detailFieldName() == FieldAccountPath) {
138             // need to find IMAccount -> hasIMContact -> imaddress
139             RDFVariable imaccount;
140             imaccount.property<nco::hasIMContact>() = imaddress;
141             imaccount.equal(QUrl(QString("telepathy:")+filter.value().toString()));
142         }
143         else if (filter.detailFieldName() == QContactOnlineAccount::FieldServiceProvider) {
144             // need to find IMAccount -> hasIMContact -> imaddress
145             RDFVariable imaccount;
146             imaccount.property<nco::hasIMContact>() = imaddress;
147             imaccount.property<nco::imDisplayName> ().isMemberOf(QStringList() << filter.value().toString());
148         }
149         else {
150             qWarning() << "QTrackerContactFetchRequest," << __FUNCTION__ << "Unsupported detail filter by QContactOnlineAccount.";
151             return QContactManager::NotSupportedError;
152         }
153         return QContactManager::NoError;
154
155     }
156     else
157     {
158         qWarning() << "QTrackerContactFetchRequest," << __FUNCTION__ << "Unsupported match flag in detail filter by QContactOnlineAccount. Use QContactFilter::MatchExactly";
159         return QContactManager::NotSupportedError;
160     }
161 }
162
163 /*!
164  * \fn matchName()
165  * \brief apply \a filter to rdf graph defined by \a variable
166  * \a variable is defining some set of nco:PersonContacts. Here, filter is applied before calling modelQuery
167  * (::tracker()->modelQuery() is then fetching data defined by this rdf graph)
168  * \sa QTrackerContactFetchRequest::applyFilterToContact
169  */
170 QContactManager::Error matchName(RDFVariable &variable, QContactDetailFilter &filter)
171 {
172     if (filter.detailDefinitionName() != QContactName::DefinitionName) {
173         qWarning() << "QTrackerContactFetchRequest," << __FUNCTION__ << "Unsupported definition name in detail filter, should be QContactName::DefinitionName";
174         return QContactManager::NotSupportedError;
175     }
176     QString filterValue = filter.value().toString();
177     QString field = filter.detailFieldName();
178     if ((filter.matchFlags() & QContactFilter::MatchExactly) == QContactFilter::MatchExactly) {
179         if (field == QContactName::FieldFirstName) {
180             variable.property<nco::nameGiven>() = LiteralValue(filterValue);
181         } else if (field == QContactName::FieldLastName) {
182             variable.property<nco::nameFamily>() = LiteralValue(filterValue);
183         } else if (field == QContactName::FieldMiddleName) {
184             variable.property<nco::nameAdditional>() = LiteralValue(filterValue);
185         } else if (field == QContactName::FieldPrefix) {
186             variable.property<nco::nameHonorificPrefix>() = LiteralValue(filterValue);
187         } else if (field == QContactName::FieldSuffix) {
188             variable.property<nco::nameHonorificSuffix>() = LiteralValue(filterValue);
189         }
190         return QContactManager::NoError;
191     } else {
192         qWarning() << "QTrackerContactFetchRequest," << __FUNCTION__ << "Unsupported match flag in detail filter by QContactName";
193         return QContactManager::NotSupportedError;
194     }
195 }
196
197 /*!
198  * \fn matchBirthday()
199  * \brief apply \a filter to rdf graph defined by \a variable
200  * \sa QTrackerContactFetchRequest::applyFilterToContact
201  */
202 QContactManager::Error matchBirthday(RDFVariable &variable, QContactDetailFilter &filt) {
203     if (filt.matchFlags() == Qt::MatchExactly && QContactBirthday::FieldBirthday == filt.detailFieldName()) {
204         RDFVariable time = variable.property<nco::birthDate> ();
205         time = LiteralValue(filt.value().toDateTime().toString(Qt::ISODate));
206         return QContactManager::NoError;
207     } else {
208         return QContactManager::NotSupportedError;
209     }
210 }
211
212 /*!
213  * \fn matchEmail()
214  * \brief apply \a filter (criteria is matching mail) to rdf graph defined by \a variable
215  * \sa QTrackerContactFetchRequest::applyFilterToContact
216  */
217 QContactManager::Error matchEmail(RDFVariable &variable, QContactDetailFilter &filt) {
218     if (filt.matchFlags() == Qt::MatchExactly && QContactEmailAddress::FieldEmailAddress == filt.detailFieldName()) {
219         RDFVariable rdfEmailAddress;
220         rdfEmailAddress = variable.property<nco::hasEmailAddress> ();
221         rdfEmailAddress.property<nco::emailAddress> () = LiteralValue(filt.value().toString());
222         return QContactManager::NoError;
223     } else {
224         return QContactManager::NotSupportedError;
225     }
226 }
227
228 /*
229  * RDFVariable describes all contacts in tracker before filter is applied.
230  * This method translates QContactFilter to tracker rdf filter. When query to tracker is made
231  * after this method, it would return only contacts that fit the filter.
232  */
233 QContactManager::Error QTrackerContactFetchRequest::applyFilterToContact(RDFVariable &variable,
234         const QContactFilter &filter)
235 {
236     if (!mRequest) {
237             return QContactManager::BadArgumentError;
238     }
239
240     if (filter.type() == QContactFilter::LocalIdFilter) {
241         QContactLocalIdFilter filt = filter;
242         if (!filt.ids().isEmpty()) {
243             if (!isMeContact()) {
244             variable.property<nco::contactLocalUID>().isMemberOf(filt.ids());
245             } else {
246                 variable == nco::default_contact_me::iri();
247             }
248         } else {
249             qWarning() << Q_FUNC_INFO << "QContactLocalIdFilter idlist is empty";
250             return QContactManager::BadArgumentError;
251         }
252     } else if (filter.type() == QContactFilter::ContactDetailFilter) {
253         QContactDetailFilter filt = filter;
254         if ( QContactPhoneNumber::DefinitionName == filt.detailDefinitionName()) {
255             return matchPhoneNumber(variable, filt);
256         }
257         else if(QContactOnlineAccount::DefinitionName == filt.detailDefinitionName()) {
258             return matchOnlineAccount(variable, filt);
259         }
260         else if (QContactName::DefinitionName == filt.detailDefinitionName()) {
261             return matchName(variable, filt);
262         }
263         else if (QContactEmailAddress::DefinitionName == filt.detailDefinitionName()) {
264             return matchEmail(variable, filt);
265         }
266         else if (QContactBirthday::DefinitionName == filt.detailDefinitionName()) {
267             return matchBirthday(variable, filt);
268         }
269         else {
270             qWarning() << __PRETTY_FUNCTION__ << "QContactTrackerEngine: Unsupported QContactFilter::ContactDetail" << filt.detailDefinitionName();
271             return QContactManager::NotSupportedError;
272         }
273     }
274     else if (filter.type() == QContactFilter::ContactDetailRangeFilter)
275     {
276         return applyDetailRangeFilterToContact(variable, filter);
277     }
278     else if (filter.type() == QContactFilter::ChangeLogFilter) {
279         const QContactChangeLogFilter& clFilter = static_cast<const QContactChangeLogFilter&>(filter);
280         // do not return facebook and telepathy contacts here
281         // it is a temp implementation for the what to offer to synchronization constraint
282         // remove usage of addressbook tag - use sync target
283         variable.property<nao::hasTag>().property<nao::prefLabel>() = LiteralValue("addressbook");
284
285         if (clFilter.eventType() == QContactChangeLogFilter::EventRemoved) { // Removed since
286             qWarning() << "QContactTrackerEngine: Unsupported QContactChangeLogFilter::Removed (contacts removed since)";
287             return QContactManager::NotSupportedError;
288         } else if (clFilter.eventType() == QContactChangeLogFilter::EventAdded) { // Added since
289             variable.property<nie::contentCreated>() >= LiteralValue(clFilter.since().toString(Qt::ISODate));
290         } else if (clFilter.eventType() == QContactChangeLogFilter::EventChanged) { // Changed since
291             variable.property<nie::contentLastModified>() >= LiteralValue(clFilter.since().toString(Qt::ISODate));
292         }
293     } else if (filter.type() == QContactFilter::UnionFilter) {
294         const QContactUnionFilter unionFilter(filter);
295         foreach (const QContactFilter& f, unionFilter.filters()) {
296             QContactManager::Error error = applyFilterToContact(variable, f);
297             if (QContactManager::NoError != error)
298                 return error;
299         }
300     }
301     else if(filter.type() == QContactFilter::InvalidFilter || filter.type() == QContactFilter::DefaultFilter)
302         return QContactManager::NoError;
303     else
304         return QContactManager::NotSupportedError;
305     return QContactManager::NoError;
306 }
307
308 //!\sa applyFilterToContact
309 QContactManager::Error QTrackerContactFetchRequest::applyDetailRangeFilterToContact(RDFVariable &variable, const QContactFilter &filter)
310 {
311     Q_ASSERT(filter.type() == QContactFilter::ContactDetailRangeFilter);
312     if (filter.type() == QContactFilter::ContactDetailRangeFilter) {
313         QContactDetailRangeFilter filt = filter;
314         // birthday range
315         if (QContactBirthday::DefinitionName == filt.detailDefinitionName()
316                 && QContactBirthday::FieldBirthday == filt.detailFieldName())
317         {
318             RDFVariable time = variable.property<nco::birthDate>();
319             if (filt.rangeFlags() & QContactDetailRangeFilter::IncludeUpper)
320                 time <= LiteralValue(filt.maxValue().toDateTime().toString(Qt::ISODate));
321             else
322                 time < LiteralValue(filt.maxValue().toDateTime().toString(Qt::ISODate));
323             if (filt.rangeFlags() & QContactDetailRangeFilter::ExcludeLower)
324                 time > LiteralValue(filt.minValue().toDateTime().toString(Qt::ISODate));
325             else
326                 time >= LiteralValue(filt.minValue().toDateTime().toString(Qt::ISODate));
327             return QContactManager::NoError;
328         }
329     }
330     qWarning() << __PRETTY_FUNCTION__ << "Unsupported detail range filter";
331     return QContactManager::NotSupportedError;
332 }
333
334
335 /*
336  * To understand why all the following methods have for affiliation param, check nco ontology:
337  * every contact has all these properties and also linked to affiliations (also contacts - nco:Role)
338  * that again have the same properties. So it was needed to make the same query 2-ce - once for contact
339  * and once for affiliations
340  */
341 RDFSelect preparePhoneNumbersQuery(RDFVariable &rdfcontact1, bool forAffiliations)
342 {
343     RDFVariable phone;
344     if (!forAffiliations)
345         phone = rdfcontact1.property<nco::hasPhoneNumber>();
346     else
347         phone = rdfcontact1.property<nco::hasAffiliation>().property<nco::hasPhoneNumber>();
348     RDFVariable type = phone.type();
349     type.property<rdfs::subClassOf>().notEqual(nco::ContactMedium::iri()); // sparql cannot handle exact type but returns all super types as junk rows
350     type.property<rdfs::subClassOf>().notEqual(rdfs::Resource::iri()); // sparql cannot handle exact type but returns all super types as junk rows
351     // therefore we eliminate those rows that are not of interest
352     // columns
353     RDFSelect queryidsnumbers;
354     queryidsnumbers.addColumn("contactId", rdfcontact1.property<nco::contactLocalUID> ());
355     queryidsnumbers.addColumn("phoneno", phone.property<nco::phoneNumber> ());
356     queryidsnumbers.addColumn("type", type);
357     queryidsnumbers.distinct();
358     return queryidsnumbers;
359 }
360
361 RDFSelect prepareEmailAddressesQuery(RDFVariable &rdfcontact1, bool forAffiliations)
362 {
363     RDFVariable email;
364     if (!forAffiliations)
365         email = rdfcontact1.property<nco::hasEmailAddress>();
366     else
367         email = rdfcontact1.property<nco::hasAffiliation>().property<nco::hasEmailAddress>();
368     const RDFVariable& type = email.type();
369     type.property<rdfs::subClassOf>().notEqual(rdfs::Resource::iri()); // sparql cannot handle exact type but returns all super types as junk rows
370     // therefore we eliminate those rows that are not of interest
371     // columns
372     RDFSelect queryidsnumbers;
373     queryidsnumbers.addColumn("contactId", rdfcontact1.property<nco::contactLocalUID> ());
374     queryidsnumbers.addColumn("emailaddress", email.property<nco::emailAddress> ());
375     queryidsnumbers.addColumn("type", type);
376     queryidsnumbers.distinct();
377     return queryidsnumbers;
378 }
379
380 RDFSelect prepareAddressesQuery(RDFVariable &rdfcontact1, bool forAffiliations)
381 {
382     RDFVariable address;
383     if (!forAffiliations)
384         address = rdfcontact1.property<nco::hasPostalAddress>();
385     else
386         address = rdfcontact1.property<nco::hasAffiliation>().property<nco::hasPostalAddress>();
387
388     RDFSelect queryidsaddresses;
389     queryidsaddresses.addColumn("contactId", rdfcontact1.property<nco::contactLocalUID> ());
390     queryidsaddresses.addColumn("street", address.property<nco::streetAddress> ());
391     queryidsaddresses.addColumn("locality", address.property<nco::locality> ());
392     queryidsaddresses.addColumn("country", address.property<nco::country> ());
393     queryidsaddresses.addColumn("postalcode", address.property<nco::postalcode> ());
394     queryidsaddresses.addColumn("region", address.property<nco::region> ());
395     queryidsaddresses.addColumn("pobox", address.property<nco::pobox> ());
396     queryidsaddresses.distinct();
397     return queryidsaddresses;
398 }
399
400
401 /*!
402  * \a contact here describes rdf graph - when making query, depending on filters applied
403  * to \a contact, query results to any set of contacts
404  */
405 RDFSelect prepareIMAddressesQuery(RDFVariable  &contact)
406 {
407     RDFSelect queryidsimacccounts;
408     // this establishes query graph relationship: imaddress that we want is a property in contact
409     RDFVariable imaddress = contact.property<nco::hasIMAddress>();
410     contact != nco::default_contact_me::iri();
411
412     // in the query, we need to get imaccount where this imaddress resides.
413     // i.e. from which local account we have established connection to imaddress
414     RDFVariable imaccount = RDFVariable::fromType<nco::IMAccount>();
415     // define link between imaccount to imaddress.
416     imaccount.property<nco::hasIMContact>() = imaddress;
417
418     queryidsimacccounts.addColumn("imaddress", imaddress);
419     queryidsimacccounts.addColumn("contactId", contact.property<nco::contactLocalUID> ());
420     queryidsimacccounts.groupBy(imaddress);
421     queryidsimacccounts.addColumn("IMId", imaddress.property<nco::imID> ());
422     queryidsimacccounts.addColumn("status", imaddress.optional().property<nco::imPresence> ());
423     queryidsimacccounts.addColumn("message", imaddress.optional().property<nco::imStatusMessage> ());
424     queryidsimacccounts.addColumn("nick", imaddress.optional().property<nco::imNickname> ());
425     queryidsimacccounts.addColumn("accountPath", imaccount); // account path
426     queryidsimacccounts.addColumn("capabilities",
427               imaddress.optional().property<nco::imCapability>().filter("GROUP_CONCAT", LiteralValue(",")));
428     queryidsimacccounts.addColumn("serviceprovider", imaccount.property<nco::imDisplayName>());
429     return queryidsimacccounts;
430 }
431
432 /*!
433  * \fn prepareIMAccountsQuery()
434  * \brief Self Contact only - define query columns to fetch info about local accounts and paths
435  */
436 RDFSelect prepareIMAccountsQuery(RDFVariable  &contact)
437 {
438     RDFVariable imAccount;
439     imAccount = RDFVariable::fromType<nco::IMAccount>();
440     RDFSelect queryidsimaccounts;
441
442     RDFVariable address = imAccount.property<nco::imAccountAddress>();
443     queryidsimaccounts.addColumn("uri", contact);
444     queryidsimaccounts.addColumn("presence", address.property<nco::imPresence>());
445     queryidsimaccounts.addColumn("message", address.property<nco::imStatusMessage>());
446     queryidsimaccounts.addColumn("nick", address.property<nco::imNickname>());
447     queryidsimaccounts.addColumn("distinct ", address.property<nco::imID>());
448     queryidsimaccounts.addColumn("address_uri", address);
449     queryidsimaccounts.addColumn("photo",address.optional().property<nco::imAvatar>());
450
451     return queryidsimaccounts;
452 }
453
454
455 const QString rdfPhoneType2QContactSubtype(const QString rdfPhoneType)
456 {
457     if( rdfPhoneType.endsWith("VoicePhoneNumber") )
458         return QContactPhoneNumber::SubTypeVoice;
459     else if ( rdfPhoneType.endsWith("CarPhoneNumber") )
460         return QContactPhoneNumber::SubTypeCar;
461     else if ( rdfPhoneType.endsWith("CellPhoneNumber") )
462         return QContactPhoneNumber::SubTypeMobile;
463     else if ( rdfPhoneType.endsWith("BbsPhoneNumber") )
464         return QContactPhoneNumber::SubTypeBulletinBoardSystem;
465     else if ( rdfPhoneType.endsWith("FaxNumber") )
466         return QContactPhoneNumber::SubTypeFax;
467     else if ( rdfPhoneType.endsWith("ModemNumber") )
468         return QContactPhoneNumber::SubTypeModem;
469     else if ( rdfPhoneType.endsWith("PagerNumber") )
470         return QContactPhoneNumber::SubTypePager;
471     else if ( rdfPhoneType.endsWith("MessagingNumber") )
472         return QContactPhoneNumber::SubTypeMessagingCapable;
473     else
474         qWarning() << Q_FUNC_INFO << "Not handled phone number type:" << rdfPhoneType;
475     return "";
476 }
477
478 /*!
479  * The method was initially created to add default fields in case client did not supply
480  * fields constraint - in that case the constraint is that default contact fields (ones
481  * being edited in contact card and synchronized) are queried.
482  */
483 void QTrackerContactFetchRequest::validateRequest()
484 {
485     Q_ASSERT(mRequest);
486     Q_ASSERT(mRequest->type() == QContactAbstractRequest::ContactFetchRequest);
487     QContactFetchHint fetchHint = mRequest->fetchHint();
488
489     if (mRequest && fetchHint.detailDefinitionsHint().isEmpty()) {
490         QStringList definitionNames;
491         definitionNames << QContactAvatar::DefinitionName
492             << QContactBirthday::DefinitionName
493             << QContactAddress::DefinitionName
494             << QContactEmailAddress::DefinitionName
495             << QContactGender::DefinitionName
496             << QContactAnniversary::DefinitionName
497             << QContactName::DefinitionName
498             << QContactOnlineAccount::DefinitionName
499             << QContactOrganization::DefinitionName
500             << QContactPhoneNumber::DefinitionName
501             << QContactUrl::DefinitionName;
502         fetchHint.setDetailDefinitionsHint(definitionNames);
503         mRequest->setFetchHint(fetchHint);
504     }
505 }
506
507 QTrackerContactFetchRequest::QTrackerContactFetchRequest(QContactAbstractRequest* request,
508                                                          QContactManagerEngine* parent) :
509     QObject(parent),
510     queryContactsReady(),
511     queryPhoneNumbersNodesPending(0),
512     queryEmailAddressNodesPending(0),
513     queryIMAccountNodesPending(0),
514     queryAddressNodesPending(0),
515     mRequest(0)
516 {
517     Q_ASSERT(parent);
518     Q_ASSERT(request);
519
520     // note that request could be QContactIdFetchRequest too
521     mRequest = qobject_cast<QContactFetchRequest*> (request);
522     QContactManagerEngine::updateRequestState(request, QContactAbstractRequest::ActiveState);
523 }
524
525 static void addColumn(const RDFVariable &variable, RDFSelect &query,
526                       const QString &name, int &columnIndexResult)
527 {
528     columnIndexResult = query.columns().count();
529     query.addColumn(name, variable);
530 }
531
532 void QTrackerContactFetchRequest::run()
533 {
534     validateRequest();
535
536     const QContactFetchHint &fetchHint(mRequest->fetchHint());
537     const QStringList &definitionNames(fetchHint.detailDefinitionsHint());
538
539     RDFVariable RDFContact = RDFVariable::fromType<nco::PersonContact>();
540     QContactManager::Error error = applyFilterToContact(RDFContact, mRequest->filter());
541     if (error != QContactManager::NoError)
542     {
543         emitFinished(error);
544         return;
545     }
546     if (definitionNames.contains(QContactPhoneNumber::DefinitionName)) {
547         queryPhoneNumbersNodes.clear();
548         queryPhoneNumbersNodesPending = 2;
549         for(int forAffiliations = 0; forAffiliations <= 1; forAffiliations++) {
550             // prepare query to get all phone numbers
551             RDFVariable rdfcontact1 = RDFVariable::fromType<nco::PersonContact>();
552             applyFilterToContact(rdfcontact1, mRequest->filter());
553             // criteria - only those with phone numbers
554             RDFSelect queryidsnumbers = preparePhoneNumbersQuery(rdfcontact1, forAffiliations);
555             queryPhoneNumbersNodes << ::tracker()->modelQuery(queryidsnumbers);
556             // need to store LiveNodes in order to receive notification from model
557             QObject::connect(queryPhoneNumbersNodes[forAffiliations].model(), SIGNAL(modelUpdated()), this, SLOT(phoneNumbersReady()));
558         }
559     }
560
561     if (definitionNames.contains(QContactEmailAddress::DefinitionName)) {
562         queryEmailAddressNodes.clear();
563         queryEmailAddressNodesPending = 2;
564         for(int forAffiliations = 0; forAffiliations <= 1; forAffiliations++) {
565             // prepare query to get all email addresses
566             RDFVariable rdfcontact1 = RDFVariable::fromType<nco::PersonContact>();
567             applyFilterToContact(rdfcontact1, mRequest->filter());
568             // criteria - only those with email addresses
569             RDFSelect queryidsnumbers = prepareEmailAddressesQuery(rdfcontact1,forAffiliations);
570             queryEmailAddressNodes << ::tracker()->modelQuery(queryidsnumbers);
571             // need to store LiveNodes in order to receive notification from model
572             QObject::connect(queryEmailAddressNodes[forAffiliations].model(), SIGNAL(modelUpdated()), this, SLOT(emailAddressesReady()));
573         }
574     }
575     if (definitionNames.contains(QContactAddress::DefinitionName)) {
576         queryAddressNodes.clear();
577         queryAddressNodesPending = 2;
578         for(int forAffiliations = 0; forAffiliations <= 1; forAffiliations++) {
579             // prepare query to get all email addresses
580             RDFVariable rdfcontact1 = RDFVariable::fromType<nco::PersonContact>();
581             applyFilterToContact(rdfcontact1, mRequest->filter());
582             // criteria - only those with email addresses
583             RDFSelect queryaddresses = prepareAddressesQuery(rdfcontact1,forAffiliations);
584             queryAddressNodes << ::tracker()->modelQuery(queryaddresses);
585             // need to store LiveNodes in order to receive notification from model
586             QObject::connect(queryAddressNodes[forAffiliations].model(), SIGNAL(modelUpdated()), this, SLOT(addressesReady()));
587         }
588     }
589
590     if (definitionNames.contains(QContactOnlineAccount::DefinitionName)) {
591         queryIMAccountNodesPending = 1;
592         RDFSelect queryidsimaccounts;
593         if(isMeContact()) {
594             // Prepare a query to get all IMAccounts
595             RDFVariable meContact = RDFVariable::fromInstance<nco::default_contact_me>();
596             queryidsimaccounts = prepareIMAccountsQuery(meContact);
597         } else {
598             RDFVariable rdfIMContact;
599             rdfIMContact = rdfIMContact.fromType<nco::PersonContact> ();
600             applyFilterToContact(rdfIMContact, mRequest->filter());
601             queryidsimaccounts = prepareIMAddressesQuery(rdfIMContact);
602         }
603         queryIMAccountNodes = ::tracker()->modelQuery(queryidsimaccounts);
604         QObject::connect(queryIMAccountNodes.model(), SIGNAL(modelUpdated()), SLOT(imAcountsReady()));
605
606     }
607
608     RDFVariable rdfContact = RDFVariable::fromType<nco::PersonContact>();
609     RDFVariable rdfAffiliation(rdfContact.optional().property<nco::hasAffiliation>());
610
611     applyFilterToContact(rdfContact, mRequest->filter());
612
613     RDFSelect query;
614
615     addColumn(rdfContact.optional().property<nco::contactLocalUID>(),
616               query, "contactId", contactIdColumn);
617     addColumn(rdfContact.optional().property<nco::nameHonorificPrefix>(),
618               query, "prefix", prefixColumn);
619     addColumn(rdfContact.optional().property<nco::nameGiven>(),
620               query, "firstname", firstNameColumn);
621     addColumn(rdfContact.optional().property<nco::nameAdditional>(),
622               query, "middlename", middleNameColumn);
623     addColumn(rdfContact.optional().property<nco::nameFamily>(),
624               query, "lastname", lastNameColumn);
625     addColumn(rdfContact.optional().property<nco::nickname>(),
626               query, "nickname", nicknameColumn);
627     addColumn(rdfContact.optional().property<nco::photo>(),
628               query, "photo", photoColumn);
629
630     if (definitionNames.contains(QContactUrl::DefinitionName)) {
631         addColumn(rdfContact.optional().property<nco::websiteUrl>(), query, "homepage", homepageColumn);
632         addColumn(rdfContact.optional().property<nco::url>(), query, "url", urlColumn);
633
634         addColumn(rdfAffiliation.property<nco::websiteUrl>(),
635                   query, "workHomepage", workHomepageColumn);
636         addColumn(rdfAffiliation.property<nco::url>(),
637                   query, "workUrl", workUrlColumn);
638     }
639
640     if (definitionNames.contains(QContactBirthday::DefinitionName)) {
641         addColumn(rdfContact.optional().property<nco::birthDate>(), query, "birth", birthColumn);
642     }
643
644     if (definitionNames.contains(QContactGender::DefinitionName)) {
645         addColumn(rdfContact.optional().property<nco::gender>(), query, "gender", genderColumn);
646     }
647
648     if (definitionNames.contains(QContactOrganization::DefinitionName)) {
649         RDFVariable rdfOrg(rdfAffiliation.optional().property<nco::org>());
650         addColumn(rdfOrg.optional().property<nco::fullname>(), query, "orgName", orgNameColumn);
651         addColumn(rdfOrg.optional().property<nco::logo>(), query, "orgLogo", orgLogoColumn);
652     }
653
654
655     // QContactAnniversary - no such thing in tracker
656     // QContactGeolocation - nco:hasLocation is not having class defined in nco yet. no properties. maybe rdfs:Resource:label
657
658     // supporting sorting only here, difficult and no requirements in UI for sorting in multivalue details (phones, emails)
659     foreach(const QContactSortOrder& sort, mRequest->sorting()) {
660         if (sort.detailDefinitionName() == QContactName::DefinitionName) {
661             if (sort.detailFieldName() == QContactName::FieldFirstName) {
662                 query.orderBy(query.columns().at(firstNameColumn).variable());
663             } else if (sort.detailFieldName() == QContactName::FieldLastName) {
664                 query.orderBy(query.columns().at(lastNameColumn).variable());
665             } else {
666                 qWarning() << "QTrackerContactFetchRequest" << "sorting by"
667                     << sort.detailDefinitionName()
668                     << sort.detailFieldName() << "is not yet supported";
669             }
670         } else {
671             qWarning() << "QTrackerContactFetchRequest" << "sorting by"
672                 << sort.detailDefinitionName()
673                 << "is not yet supported";
674         }
675     }
676
677     liveQuery = ::tracker()->modelQuery(query);
678     // need to store LiveNodes in order to receive notification from model
679     QObject::connect(liveQuery.model(), SIGNAL(modelUpdated()), this, SLOT(contactsReady()));
680
681     // wait for query result notifications
682     mEventLoop.reset(new QEventLoop());
683     QContactManager::Error err = (QContactManager::Error)mEventLoop->exec();
684     mEventLoop.reset(0);
685     emitFinished(err);
686 }
687
688 bool detailExisting(const QString &definitionName, const QContact &contact, const QContactDetail &adetail)
689 {
690     QList<QContactDetail> details = contact.details(definitionName);
691     foreach(const QContactDetail &detail, details) {
692         if (detail == adetail) {
693             return true;
694         }
695     }
696     return false;
697 }
698
699 void QTrackerContactFetchRequest::contactsReady()
700 {
701     queryContactsReady = true;
702     processResults();
703 }
704
705 void QTrackerContactFetchRequest::processResults()
706 {
707     // queries' results are not received in same order as queries were run
708     if (!queryContactsReady || queryAddressNodesPending || queryEmailAddressNodesPending
709             || queryIMAccountNodesPending || queryPhoneNumbersNodesPending) {
710         return;
711     }
712     // 1) process contacts:
713     // 2) process IMAddresses: queryIMAccountNodes
714     // 3) process phonenumbers: queryPhoneNumbersNodes
715     // 4) process emails: queryEmailAddressesNodes
716     // 5) process addresses: queryAddressesNodes
717     // 6) update display label details
718     Q_ASSERT( mRequest );
719     if (!mRequest) {
720         mEventLoop->exit(QContactManager::UnspecifiedError);
721         return;
722     }
723
724     QContactManagerEngine *engine = qobject_cast<QContactManagerEngine *>(parent());
725     Q_ASSERT(engine);
726
727     // 1) process contacts:
728     const QContactFetchHint &fetchHint(mRequest->fetchHint());
729     const QStringList &definitionNames(fetchHint.detailDefinitionsHint());
730
731     for(int i = 0; i < liveQuery->rowCount(); i++) {
732         QContactLocalId contactId = 0;
733         bool contactIdValid = false;
734
735         if (isMeContact()) {
736             if (engine) {
737                QContactManager::Error error;
738                contactId = engine->selfContactId(&error);
739                contactIdValid = true;
740             }
741         } else {
742             contactId = liveData(i, contactIdColumn).toUInt(&contactIdValid);
743         }
744
745         if (!contactIdValid) {
746             qWarning() << Q_FUNC_INFO << "Invalid contact ID: "
747                        << liveData(i, contactIdColumn).toString();
748             continue;
749         }
750
751         QContactId id; id.setLocalId(contactId);
752
753         if(engine)
754             id.setManagerUri(engine->managerUri());
755
756         QContact contact; // one we will be filling with this row
757         contact.setId(id);
758
759         // using redundancy to get less queries to tracker - it is possible
760         // that rows are repeating: information for one contact is in several rows
761         // that's why we update existing contact and append details to it if they
762         // are not already in QContact
763         QHash<quint32, int>::const_iterator it = id2ContactLookup.find(contactId);
764         //
765         int index = result.size(); // where to place new contact
766         if (id2ContactLookup.end() != it) {
767             if (it.value() < result.size() && it.value() >= 0) {
768                 index = it.value();
769                 contact = result[index];
770             }
771             Q_ASSERT(liveData(i, contactIdColumn).toUInt() == contact.localId());
772         }
773
774         readFromQueryRowToContact(contact ,i);
775         //to prevent me contact getting list in the contact list
776         if (!isMeContact()) {
777             addContactToResultSet(contact);
778         }
779     }
780
781     /*
782      * 2) process IMAddresses _ To be replaced with derivedObjects
783      */
784      if (definitionNames.contains(QContactOnlineAccount::DefinitionName)) {
785          processQueryIMContacts(queryIMAccountNodes);
786      }
787
788     // 3) process phonenumbers: queryPhoneNumbersNodes
789     if (definitionNames.contains(QContactPhoneNumber::DefinitionName)) {
790         Q_ASSERT(queryPhoneNumbersNodes.size() == 2);
791         for( int cnt = 0; cnt < queryPhoneNumbersNodes.size(); cnt++) {
792             processQueryPhoneNumbers(queryPhoneNumbersNodes[cnt], cnt);
793         }
794     }
795
796     // 4) process emails: queryEmailAddressesNodes
797     if (definitionNames.contains(QContactEmailAddress::DefinitionName)) {
798         Q_ASSERT(queryEmailAddressNodes.size() == 2);
799         for (int cnt = 0; cnt < queryEmailAddressNodes.size(); cnt++) {
800             processQueryEmailAddresses(queryEmailAddressNodes[cnt], cnt);
801         }
802     }
803
804     // 5) process addresses: queryAddressesNodes
805     if (definitionNames.contains(QContactAddress::DefinitionName)) {
806         Q_ASSERT(queryAddressNodes.size() == 2);
807         for (int cnt = 0; cnt < queryAddressNodes.size(); cnt++) {
808             processQueryAddresses(queryAddressNodes[cnt], cnt);
809         }
810     }
811     // 6) update display labels
812     for(int i = 0; i < result.count(); i++)
813     {
814         QContact &cont(result[i]);
815         QContactDisplayLabel dl = cont.detail(QContactDisplayLabel::DefinitionName);
816         if (dl.label().isEmpty()) {
817             QContactManager::Error synthError;
818             engine->setContactDisplayLabel(&cont, engine->synthesizedDisplayLabel(cont, &synthError));
819         }
820     }
821
822     Q_ASSERT(not liveQuery.model()->canFetchMore(QModelIndex()));
823
824     Q_ASSERT(mEventLoop);
825     if (mEventLoop) {
826         mEventLoop->exit(QContactManager::NoError);
827     }
828 }
829
830 void QTrackerContactFetchRequest::emitFinished(QContactManager::Error error)
831 {
832     Q_ASSERT(mRequest);
833     if(mRequest) {
834         QContactManagerEngine::updateContactFetchRequest(mRequest, result, error, QContactAbstractRequest::FinishedState);
835     }
836 }
837
838 /*!
839  *  Appending contact to \sa result, id2ContactLookup,  of the request - to add it as new or to replace existing in result.
840  */
841 void QTrackerContactFetchRequest::addContactToResultSet(QContact &contact)
842 {
843     QHash<quint32, int>::const_iterator it = id2ContactLookup.find(contact.localId());
844     int index = -1; // where to place new contact, -1 - not defined
845     if (id2ContactLookup.end() != it) {
846         if (it.value() < result.size() && it.value() >= 0)
847             index = it.value();
848     }
849     QContact *contact_ = &contact;
850     if ( -1 == index ) {
851         result.append(*contact_);
852         id2ContactLookup[contact_->localId()] = result.size()-1;
853     } else {
854         result[index] = *contact_;
855         id2ContactLookup[contact_->localId()] = index;
856     }
857 }
858
859 /*!
860  * brief Processes one query record-row during read from tracker to QContact.
861  * Order or columns in query is fixed to order defined in \sa run()
862  */
863 void QTrackerContactFetchRequest::readFromQueryRowToContact(QContact &contact, int i)
864 {
865     Q_ASSERT( mRequest ); // this is handled already in caller
866     if( !mRequest )
867         return;
868
869     QContactName name = contact.detail(QContactName::DefinitionName);
870     name.setPrefix(liveData(i, prefixColumn).toString());
871     name.setFirstName(liveData(i, firstNameColumn).toString());
872     name.setMiddleName(liveData(i, middleNameColumn).toString());
873     name.setLastName(liveData(i, lastNameColumn).toString());
874     contact.saveDetail(&name);
875
876     QContactAvatar avatar = contact.detail(QContactAvatar::DefinitionName);
877     avatar.setImageUrl(QUrl(liveData(i, photoColumn).toString())); // FIXME?
878     if (avatar.imageUrl().isValid()) {
879         contact.saveDetail(&avatar);
880     }
881
882     QContactNickname nickname = contact.detail(QContactNickname::DefinitionName);
883     nickname.setNickname(liveData(i, nicknameColumn).toString());
884     contact.saveDetail(&nickname);
885
886     const QContactFetchHint &fetchHint(mRequest->fetchHint());
887     const QStringList &definitionNames(fetchHint.detailDefinitionsHint());
888
889     if (definitionNames.contains(QContactUrl::DefinitionName)) {
890         // check query preparation (at the moment in constructor TODO refactor)
891         // home website
892         // if it is websiteUrl then interpret as homepage, if it is nco:url then fovourite url
893         QContactUrl url;
894
895         url.setSubType(QContactUrl::SubTypeHomePage);
896         url.setContexts(QContactUrl::ContextHome);
897         url.setUrl(liveData(i, homepageColumn).toString());
898
899         if (url.url().isEmpty()) {
900             // website url is at the same time url, so we handle duplication here
901             // if only url then set it as favourite
902             url.setUrl(liveData(i, urlColumn).toString());
903             url.setSubType(QContactUrl::SubTypeFavourite);
904         }
905
906         if (not url.url().isEmpty() &&
907             not detailExisting(QContactUrl::DefinitionName, contact, url)) {
908             contact.saveDetail(&url);
909         }
910         // office website
911         QContactUrl workUrl;
912
913         workUrl.setContexts(QContactUrl::ContextWork);
914         workUrl.setSubType(QContactUrl::SubTypeHomePage);
915         workUrl.setUrl(liveData(i, workHomepageColumn).toString());
916
917         if (workUrl.url().isEmpty()) {
918             workUrl.setUrl(liveData(i, workUrlColumn).toString());
919             workUrl.setSubType(QContactUrl::SubTypeFavourite);
920         }
921         if (not workUrl.url().isEmpty() &&
922             not detailExisting(QContactUrl::DefinitionName, contact, workUrl)) {
923             contact.saveDetail(&workUrl);
924         }
925     }
926     if (definitionNames.contains(QContactBirthday::DefinitionName)) {
927         QVariant var = liveData(i, birthColumn);
928         if (!var.toString().isEmpty() /* enable reading wrong && var.toDate().isValid()*/) {
929             QContactBirthday birth = contact.detail(QContactBirthday::DefinitionName);
930             birth.setDate(var.toDate());
931             contact.saveDetail(&birth);
932         }
933     }
934     if (definitionNames.contains(QContactGender::DefinitionName)) {
935         QString var = liveData(i, genderColumn).toString();
936         if (!var.isEmpty()) {
937             QContactGender g = contact.detail(QContactGender::DefinitionName);
938             g.setGender(var);
939             contact.saveDetail(&g);
940         }
941     }
942     if (definitionNames.contains(QContactOrganization::DefinitionName)) {
943         const QString name(liveData(i, orgNameColumn).toString());
944         const QString logo(liveData(i, orgLogoColumn).toString());
945
946         if (not name .isEmpty() || not logo.isEmpty()) {
947             QContactOrganization org;
948
949             org.setName(name );
950             org.setLogoUrl(QUrl(logo));
951
952             if (not detailExisting(QContactOrganization::DefinitionName, contact, org)) {
953                 contact.saveDetail(&org);
954             }
955         }
956     }
957
958 }
959
960
961 void QTrackerContactFetchRequest::phoneNumbersReady()
962 {
963     queryPhoneNumbersNodesPending--;
964     processResults();
965 }
966
967 void QTrackerContactFetchRequest::emailAddressesReady()
968 {
969     queryEmailAddressNodesPending--;
970     processResults();
971 }
972
973 void QTrackerContactFetchRequest::addressesReady()
974 {
975     queryAddressNodesPending--;
976     processResults();
977 }
978
979 void QTrackerContactFetchRequest::imAcountsReady()
980 {
981     queryIMAccountNodesPending--;
982     processResults();
983 }
984
985 /*!
986  * An internal helper method for converting nco:PhoneNumber subtype to
987  * QContactPhoneNumber:: subtype attribute
988  */
989 void QTrackerContactFetchRequest::processQueryPhoneNumbers(SopranoLive::LiveNodes queryPhoneNumbers,
990                                                            bool affiliationNumbers )
991 {
992     if (queryPhoneNumbersNodesPending)
993         qCritical() << Q_FUNC_INFO << "Phonenumbers query was supposed to be ready and it is not.";
994     for (int i = 0; i < queryPhoneNumbers->rowCount(); i++) {
995         // ignore if next one is the same - asked iridian about making query to ignore supertypes
996         // TODO remove after his answer
997         if ( i-1 >= 0
998              && (queryPhoneNumbers->index(i, 0).data().toString()
999                  == queryPhoneNumbers->index(i-1, 0).data().toString())
1000              && (queryPhoneNumbers->index(i, 1).data().toString()
1001                  == queryPhoneNumbers->index(i-1, 1).data().toString())) {
1002             // this is for ignoring duplicates. bad approach, asked iridian about
1003             // how to eliminate super types in query results
1004             continue;
1005         }
1006
1007         QString subtype = rdfPhoneType2QContactSubtype(queryPhoneNumbers->index(i, 2).data().toString());
1008         QContactLocalId contactid = queryPhoneNumbers->index(i, 0).data().toUInt();
1009
1010         QHash<quint32, int>::const_iterator it = id2ContactLookup.find(contactid);
1011         if (it != id2ContactLookup.end() && it.key() == contactid && it.value() >= 0 && it.value() < result.size())
1012         {
1013             QContactPhoneNumber number;
1014             if( affiliationNumbers )
1015                 number.setContexts(QContactPhoneNumber::ContextWork);
1016             else
1017                 number.setContexts(QContactPhoneNumber::ContextHome);
1018             number.setNumber(queryPhoneNumbers->index(i, 1).data().toString());
1019             number.setSubTypes(subtype);
1020             result[it.value()].saveDetail(&number);
1021         }
1022         else
1023             Q_ASSERT(false);
1024     }
1025     Q_ASSERT(not queryPhoneNumbers.model()->canFetchMore(QModelIndex()));
1026 }
1027
1028 void QTrackerContactFetchRequest::processQueryEmailAddresses( SopranoLive::LiveNodes queryEmailAddresses,
1029                                                               bool affiliationEmails)
1030 {
1031     if (queryEmailAddressNodesPending)
1032         qCritical() << Q_FUNC_INFO << "Email query was supposed to be ready and it is not.";
1033     for (int i = 0; i < queryEmailAddresses->rowCount(); i++) {
1034         // ignore if next one is the same - asked iridian about making query to ignore supertypes
1035         // TODO remove after his answer
1036         if ( i-1 >= 0
1037              && (queryEmailAddresses->index(i, 0).data().toString()
1038                  == queryEmailAddresses->index(i-1, 0).data().toString())
1039              && (queryEmailAddresses->index(i, 1).data().toString()
1040                  == queryEmailAddresses->index(i-1, 1).data().toString())) {
1041             // this is for ignoring duplicates. bad approach, asked iridian
1042             // about how to eliminate super types in query results
1043             continue;
1044         }
1045
1046         //QString subtype = rdfPhoneType2QContactSubtype(queryEmailAddresses->index(i, 2).data().toString());
1047         QContactLocalId contactid = queryEmailAddresses->index(i, 0).data().toUInt();
1048
1049         QHash<quint32, int>::const_iterator it = id2ContactLookup.find(contactid);
1050         if (it != id2ContactLookup.end() && it.key() == contactid && it.value() >= 0 && it.value() < result.size())
1051         {
1052             QContactEmailAddress email;
1053             if (affiliationEmails)
1054                 email.setContexts(QContactEmailAddress::ContextWork);
1055             else
1056                 email.setContexts(QContactEmailAddress::ContextHome);
1057             email.setEmailAddress(queryEmailAddresses->index(i, 1).data().toString());
1058             //email.setSubTypes(subtype);
1059             result[it.value()].saveDetail(&email);
1060         }
1061         else
1062             Q_ASSERT(false);
1063     }
1064     Q_ASSERT(not queryEmailAddresses.model()->canFetchMore(QModelIndex()));
1065 }
1066
1067 void QTrackerContactFetchRequest::processQueryAddresses( SopranoLive::LiveNodes queryAddresses,
1068                                                               bool affiliationAddresses)
1069 {
1070     if (queryAddressNodesPending)
1071         qCritical() << Q_FUNC_INFO << "Address query was supposed to be ready and it is not.";
1072     for (int i = 0; i < queryAddresses->rowCount(); i++) {
1073         if ( i-1 >= 0
1074              && (queryAddresses->index(i, 0).data().toString()
1075                  == queryAddresses->index(i-1, 0).data().toString())
1076              && (queryAddresses->index(i, 1).data().toString()
1077                  == queryAddresses->index(i-1, 1).data().toString())) {
1078             continue;
1079         }
1080
1081         QContactLocalId contactid = queryAddresses->index(i, 0).data().toUInt();
1082
1083         QHash<quint32, int>::const_iterator it = id2ContactLookup.find(contactid);
1084         if (it != id2ContactLookup.end() && it.key() == contactid && it.value() >= 0 && it.value() < result.size())
1085         {
1086             QContactAddress address;
1087
1088             address.setStreet(queryAddresses->index(i, 1).data().toString());
1089             address.setLocality(queryAddresses->index(i, 2).data().toString());
1090             address.setCountry(queryAddresses->index(i, 3).data().toString());
1091             address.setPostcode(queryAddresses->index(i, 4).data().toString());
1092             address.setRegion(queryAddresses->index(i, 5).data().toString());
1093             address.setPostOfficeBox(queryAddresses->index(i, 6).data().toString());
1094
1095
1096             if (affiliationAddresses)
1097                 address.setContexts(QContactEmailAddress::ContextWork);
1098             else
1099                 address.setContexts(QContactEmailAddress::ContextHome);
1100             result[it.value()].saveDetail(&address);
1101         }
1102         else
1103             Q_ASSERT(false);
1104     }
1105     Q_ASSERT(not queryAddresses.model()->canFetchMore(QModelIndex()));
1106 }
1107
1108 /*!
1109  * \brief Processes one query record-row during read from tracker to QContactOnlineAccount.
1110  * Order or columns in query is fixed to order defined in \sa prepareIMAddressesQuery()
1111  */
1112 QContactOnlineAccount QTrackerContactFetchRequest::getOnlineAccountFromIMQuery(LiveNodes imAccountQuery, int queryRow)
1113 {
1114     QContactOnlineAccount account;
1115     if(isMeContact()) {
1116         account = getIMAccountFromIMQuery(imAccountQuery, queryRow);
1117     } else {
1118         account = getIMContactFromIMQuery(imAccountQuery, queryRow);
1119     }
1120     return account;
1121 }
1122
1123 /*!
1124  * \brief processes IMQuery results. \sa prepareIMAddressesQuery, contactsReady
1125  */
1126 void QTrackerContactFetchRequest::processQueryIMContacts(SopranoLive::LiveNodes queryIMContacts)
1127 {
1128     QContactManagerEngine *engine = qobject_cast<QContactManagerEngine *>(parent());
1129     Q_ASSERT(engine);
1130     if (!mRequest  || !engine) {
1131         return;
1132     }
1133
1134     for (int i = 0; i < queryIMContacts->rowCount(); i++) {
1135         QContactOnlineAccount account = getOnlineAccountFromIMQuery(queryIMContacts, i);
1136         QContactPresence presence = getPresence(queryIMContacts, i);
1137         account.setLinkedDetailUris(presence.detailUri());
1138         presence.setLinkedDetailUris(account.detailUri());
1139         QContactLocalId contactid = queryIMContacts->index(i, IMContact::ContactId).data().toUInt();
1140         // Need special treatment for me contact since the avatar is not working :(
1141         //
1142         if (isMeContact()) {
1143             QString avatarURI = queryIMAccountNodes->index(i, IMAccount::ContactAvatar).data().toString();
1144             QContact meContact;
1145             QContactLocalId meContactLocalId;
1146             QContactManager::Error error;
1147             meContactLocalId = engine->selfContactId(&error);
1148             QContactId id; id.setLocalId(meContactLocalId);
1149             meContact.setId(id);
1150             QContactAvatar avatar = meContact.detail(QContactAvatar::DefinitionName);
1151             avatar.setImageUrl(QUrl(avatarURI)); // FIXME?
1152             //nick
1153
1154             QContactNickname qnick = meContact.detail(QContactNickname::DefinitionName);
1155             QString nick = queryIMAccountNodes->index(i, IMAccount::ContactNickname).data().toString(); // nick
1156             qnick.setNickname(nick);
1157
1158             if (!avatarURI.isEmpty()) {
1159                 meContact.saveDetail(&account);
1160                 meContact.saveDetail(&avatar);
1161                 meContact.saveDetail(&qnick);
1162                 addContactToResultSet(meContact);
1163             }
1164         }
1165
1166         QHash<quint32, int>::const_iterator it = id2ContactLookup.find(contactid);
1167         if (it != id2ContactLookup.end() && it.key() == contactid && it.value() >= 0 && it.value() < result.size())
1168         {
1169            result[it.value()].saveDetail(&account);
1170            result[it.value()].saveDetail(&presence);
1171         }
1172     }
1173     Q_ASSERT(not queryIMContacts.model()->canFetchMore(QModelIndex()));
1174 }
1175
1176 bool  QTrackerContactFetchRequest::isMeContact() {
1177
1178     if (!mRequest) {
1179         return false;
1180     }
1181
1182     if (mRequest->filter().type() == QContactFilter::LocalIdFilter) {
1183          QContactManagerEngine *engine = dynamic_cast<QContactManagerEngine*>(parent());
1184          if(!engine) {
1185              qWarning() << __PRETTY_FUNCTION__ << ": Could not get QContactManager. Cannot retrieve IMAccounts for me-contact.";
1186              return false;
1187          }
1188
1189         QContactManager::Error e;
1190         QContactLocalId selfId = engine->selfContactId(&e);
1191         QContactLocalIdFilter filt = mRequest->filter();
1192         if (filt.ids().contains(selfId)) {
1193             return true;
1194         }
1195     }
1196     return false;
1197 }
1198
1199
1200 QContactOnlineAccount QTrackerContactFetchRequest::getIMAccountFromIMQuery(LiveNodes imAccountQuery, int queryRow) {
1201     QContactOnlineAccount account;
1202
1203     // Custom value in QContactrOnlineAccount detail to store the account path to - to determine in My Profile to ignore the ring-account.
1204     QString imid = imAccountQuery->index(queryRow, IMAccount::ContactIMId).data().toString();
1205     account.setValue("Account", imid); // IMId
1206     // the same is supposed to be in FieldAccountUri field
1207     account.setValue(QContactOnlineAccount::FieldAccountUri, imid); // IMId
1208     account.setDetailUri(QString::fromLatin1("account:")+imid+QString::fromLatin1("/")+imAccountQuery->index(queryRow, IMContact::AccountPath).data().toString());
1209     return account;
1210 }
1211
1212 QContactPresence QTrackerContactFetchRequest::getPresence(LiveNodes imAccountQuery, int queryRow)
1213 {
1214
1215     QContactPresence contactPresence;
1216     QString presence = imAccountQuery->index(queryRow, IMContact::ContactPresence).data().toString(); // imPresence iri
1217     presence = presence.right(presence.length() - presence.lastIndexOf("presence-status"));
1218     contactPresence.setPresenceStateText(presence);
1219     contactPresence.setPresenceState(presenceConversion[presence]);
1220
1221     contactPresence.setCustomMessage(imAccountQuery->index(queryRow, IMContact::ContactMessage).data().toString());
1222     contactPresence.setNickname(imAccountQuery->index(queryRow, IMContact::ContactNickname).data().toString());
1223
1224     contactPresence.setDetailUri(QString::fromLatin1("presence:")+imAccountQuery->index(queryRow, IMContact::ContactIMId).data().toString() + QString::fromLatin1("/") +
1225             imAccountQuery->index(queryRow, IMContact::AccountPath).data().toString());
1226
1227     return contactPresence;
1228 }
1229
1230
1231 QContactPresence QTrackerContactFetchRequest::getAccountPresence(LiveNodes imAccountQuery, int queryRow)
1232 {
1233
1234     QContactPresence contactPresence;
1235     QString presence = imAccountQuery->index(queryRow, IMAccount::ContactPresence).data().toString(); // imPresence iri
1236     presence = presence.right(presence.length() - presence.lastIndexOf("presence-status"));
1237     contactPresence.setPresenceStateText(presence);
1238     contactPresence.setPresenceState(presenceConversion[presence]);
1239     contactPresence.setCustomMessage(imAccountQuery->index(queryRow, IMAccount::ContactMessage).data().toString());
1240     contactPresence.setNickname(imAccountQuery->index(queryRow, IMAccount::ContactNickname).data().toString());
1241
1242     contactPresence.setDetailUri("presence:"+imAccountQuery->index(queryRow, IMAccount::ContactIMId).data().toString());
1243
1244     return contactPresence;
1245 }
1246
1247
1248 QContactOnlineAccount QTrackerContactFetchRequest::getIMContactFromIMQuery(LiveNodes imContactQuery, int queryRow) {
1249     QContactOnlineAccount account;
1250
1251     QString imid = imContactQuery->index(queryRow, IMContact::ContactIMId).data().toString();
1252     account.setValue("Account", imid); // IMId
1253     account.setAccountUri(imid);
1254     QString accountPath = imContactQuery->index(queryRow, IMContact::AccountPath).data().toString();
1255     account.setDetailUri(QString::fromLatin1("account:")+imid+QString::fromLatin1("/")+accountPath);
1256
1257     if (!accountPath.isEmpty()) {
1258         QStringList decoded = accountPath.split(":");
1259         // taking out the prefix "telepathy:"
1260         account.setValue(FieldAccountPath, decoded.value(1));
1261     }
1262
1263
1264     // XXX FIXME -- TODO VIA PRESENCE
1265     //account.setNickname(imContactQuery->index(queryRow, IMContact::ContactNickname).data().toString()); // nick
1266
1267     QString cap = imContactQuery->index(queryRow, IMContact::Capabilities).data().toString();
1268     cap = cap.right(cap.length() - cap.lastIndexOf("im-capability"));
1269     account.setValue(QContactOnlineAccount::FieldCapabilities, cap);
1270
1271     // XXX FIXME -- TODO VIA PRESENCE
1272     //QString presence = imContactQuery->index(queryRow, IMContact::ContactPresence).data().toString(); // imPresence iri
1273     //presence = presence.right(presence.length() - presence.lastIndexOf("presence-status"));
1274     //account.setPresence(presenceConversion[presence]);
1275     //
1276     //account.setStatusMessage(imContactQuery->index(queryRow, IMContact::ContactMessage).data().toString()); // imStatusMessage
1277
1278     account.setServiceProvider(imContactQuery->index(queryRow, IMContact::ServiceProvider).data().toString()); // service name
1279     return account;
1280 }
1281
1282 QTrackerContactFetchRequest::~QTrackerContactFetchRequest()
1283 {
1284     if (mEventLoop) {
1285         mEventLoop->exit(QContactManager::NoError);
1286     }
1287 }