New: contacts() and contactsIds methods, reporting filter errors
[qtcontacts-tracker:qtcontacts-tracker.git] / qtrackercontactasyncrequest.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
43
44
45 #include <QTimer>
46 #include <qtcontacts.h>
47 #include <QtTracker/ontologies/nie.h>
48 #include <QtTracker/ontologies/nco.h>
49 #include <qtrackercontactasyncrequest.h>
50 #include <QHash>
51
52 using namespace SopranoLive;
53
54 class ConversionLookup: public QHash<QString,QString>
55 {
56 public:
57     ConversionLookup& operator<<(const QPair<QString, QString> &conversion)
58     {
59         this->insert(conversion.first, conversion.second);
60         return *this;
61     }
62 };
63
64
65 const QString FieldQContactLocalId("QContactLocalId");
66 const QString FieldAccountPath("AccountPath");
67 const ConversionLookup presenceConversion(ConversionLookup()
68             <<QPair<QString, QString>("presence-status-offline", QContactOnlineAccount::PresenceOffline)
69             <<QPair<QString, QString>("presence-status-available", QContactOnlineAccount::PresenceAvailable)
70             <<QPair<QString, QString>("presence-status-away", QContactOnlineAccount::PresenceAway)
71             <<QPair<QString, QString>("presence-status-extended-away", QContactOnlineAccount::PresenceExtendedAway)
72             <<QPair<QString, QString>("presence-status-busy", QContactOnlineAccount::PresenceBusy)
73             <<QPair<QString, QString>("presence-status-unknown", QContactOnlineAccount::PresenceUnknown)
74             <<QPair<QString, QString>("presence-status-hidden", QContactOnlineAccount::PresenceHidden)
75             <<QPair<QString, QString>("presence-status-dnd", QContactOnlineAccount::PresenceBusy)
76 );
77
78 void matchPhoneNumber(RDFVariable &variable, QContactDetailFilter &filter)
79 {
80     // This here is the first implementation of filtering that takes into account also affiliations.
81     // needs to be applied for other filters too - TODO
82     RDFVariable officeContact;
83     RDFVariable homeContact;
84
85     RDFVariable rdfPhoneNumber;
86     rdfPhoneNumber = homeContact.property<nco::hasPhoneNumber>().property<nco::phoneNumber>();
87
88     RDFVariable rdfOfficePhoneNumber;
89     rdfOfficePhoneNumber = officeContact.property<nco::hasAffiliation>().property<nco::hasPhoneNumber>().property<nco::phoneNumber>();
90
91     QString filterValue = filter.value().toString();
92     if (filter.matchFlags() == Qt::MatchEndsWith)
93     {
94         QSettings settings(QSettings::IniFormat, QSettings::UserScope, "Nokia","Trackerplugin");
95         int matchDigitCount = settings.value("phoneNumberMatchDigitCount", "7").toInt();
96         filterValue = filterValue.right(matchDigitCount);
97         qDebug() << "match with:" << matchDigitCount << ":" << filterValue;
98         rdfPhoneNumber.hasSuffix(filterValue);
99         rdfOfficePhoneNumber.hasSuffix(filterValue);
100     }
101     else
102     {   // default to exact match
103         rdfOfficePhoneNumber.matchesRegexp(filterValue);
104         rdfPhoneNumber.matchesRegexp(filterValue);
105     }
106     // This is the key part, including both contacts and affiliations
107     variable.isMemberOf(RDFVariableList()<<homeContact);// TODO report bug doesnt work in tracker <<officeContact);
108 }
109
110 void matchOnlineAccount(RDFVariable &variable, QContactDetailFilter &filter)
111 {
112     if ((filter.matchFlags() & QContactFilter::MatchExactly) == QContactFilter::MatchExactly)
113     {
114         if (filter.detailFieldName() == "Account" || filter.detailFieldName() == QContactOnlineAccount::FieldAccountUri)
115         {
116             variable.property<nco::imContactId> ().isMemberOf(QStringList() << filter.value().toString());
117         }
118         else if (filter.detailFieldName() == FieldAccountPath)
119         {
120             // as it uses telepathy:account path
121             variable.property<nco::fromIMAccount>().equal(QUrl(QString("telepathy:")+filter.value().toString()));
122         }
123         else if (filter.detailFieldName() == QContactOnlineAccount::FieldServiceProvider)
124         {
125             variable.property<nco::fromIMAccount>().property<nco::imDisplayName> ().isMemberOf(QStringList() << filter.value().toString());
126         }
127         else
128             qWarning() << "QTrackerContactFetchRequest," << __FUNCTION__
129                     << "Unsupported detail filter by QContactOnlineAccount.";
130     }
131     else
132     {
133         qWarning() << "QTrackerContactFetchRequest," << __FUNCTION__
134                 << "Unsupported match flag in detail filter by QContactOnlineAccount. Use QContactFilter::MatchExactly";
135     }
136 }
137
138 void matchName(RDFVariable &variable, QContactDetailFilter &filter)
139 {
140     if (filter.detailDefinitionName() != QContactName::DefinitionName) {
141         qWarning() << "QTrackerContactFetchRequest," << __FUNCTION__
142                    << "Unsupported definition name in detail filter, should be QContactName::DefinitionName";
143         return;
144     }
145     QString filterValue = filter.value().toString();
146     QString field = filter.detailFieldName();
147     if ((filter.matchFlags() & QContactFilter::MatchExactly) == QContactFilter::MatchExactly) {
148         if (field == QContactName::FieldFirst) {
149             variable.property<nco::nameGiven>() = LiteralValue(filterValue);
150         } else if (field == QContactName::FieldLast) {
151             variable.property<nco::nameFamily>() = LiteralValue(filterValue);
152         } else if (field == QContactName::FieldMiddle) {
153             variable.property<nco::nameAdditional>() = LiteralValue(filterValue);
154         } else if (field == QContactName::FieldPrefix) {
155             variable.property<nco::nameHonorificPrefix>() = LiteralValue(filterValue);
156         } else if (field == QContactName::FieldSuffix) {
157             variable.property<nco::nameHonorificSuffix>() = LiteralValue(filterValue);
158         }
159     } else {
160         qWarning() << "QTrackerContactFetchRequest," << __FUNCTION__
161                    << "Unsupported match flag in detail filter by QContactName";
162     }
163 }
164
165 /*
166  * RDFVariable describes all contacts in tracker before filter is applied.
167  * This method translates QContactFilter to tracker rdf filter. When query is made
168  * after this method, it would return only contacts that fit the filter.
169  */
170 QContactManager::Error QTrackerContactFetchRequest::applyFilterToContact(RDFVariable &variable,
171         const QContactFilter &filter)
172 {
173     if (filter.type() == QContactFilter::LocalIdFilter) {
174         QContactLocalIdFilter filt = filter;
175         if (!filt.ids().isEmpty()) {
176             variable.property<nco::contactUID>().isMemberOf(filt.ids());
177         } else {
178             qWarning() << Q_FUNC_INFO << "QContactLocalIdFilter idlist is empty";
179             return QContactManager::BadArgumentError;
180         }
181     } else if (filter.type() == QContactFilter::ContactDetailFilter) {
182         // this one is tricky as we need to match in contacts or in affiliations
183
184         QContactDetailFilter filt = filter;
185         if ( QContactPhoneNumber::DefinitionName == filt.detailDefinitionName()
186              && QContactPhoneNumber::FieldNumber == filt.detailFieldName()) {
187             matchPhoneNumber(variable, filt);
188         }
189         else if(QContactOnlineAccount::DefinitionName == filt.detailDefinitionName())
190         {
191             matchOnlineAccount(variable, filt);
192         }
193         else if (QContactName::DefinitionName == filt.detailDefinitionName()) {
194             matchName(variable, filt);
195         }
196         else if (filt.matchFlags() == Qt::MatchExactly) {
197             if (QContactEmailAddress::DefinitionName == filt.detailDefinitionName()
198                        && QContactEmailAddress::FieldEmailAddress == filt.detailFieldName()) {
199                 RDFVariable rdfEmailAddress;
200                 rdfEmailAddress = variable.property<nco::hasEmailAddress>();
201                 rdfEmailAddress.property<nco::emailAddress>() = LiteralValue(filt.value().toString());
202             } else {
203                 qWarning() << __PRETTY_FUNCTION__ << "QContactTrackerEngine: Unsupported QContactFilter::ContactDetail"
204                     << filt.detailDefinitionName();
205                 return QContactManager::NotSupportedError;
206             }
207         }
208     }
209     else if (filter.type() == QContactFilter::ContactDetailRangeFilter)
210     {
211         return applyDetailRangeFilterToContact(variable, filter);
212     }
213     else if (filter.type() == QContactFilter::ChangeLogFilter) {
214         const QContactChangeLogFilter& clFilter = static_cast<const QContactChangeLogFilter&>(filter);
215         // do not return facebook and telepathy contacts here
216         // it is a temp implementation for the what to offer to synchronization constraint
217         variable.property<nao::hasTag>().property<nao::prefLabel>() = LiteralValue("addressbook");
218
219         if (clFilter.eventType() == QContactChangeLogFilter::EventRemoved) { // Removed since
220             qWarning() << "QContactTrackerEngine: Unsupported QContactChangeLogFilter::Removed (contacts removed since)";
221             return QContactManager::NotSupportedError;
222         } else if (clFilter.eventType() == QContactChangeLogFilter::EventAdded) { // Added since
223             variable.property<nie::contentCreated>() >= LiteralValue(clFilter.since().toString(Qt::ISODate));
224         } else if (clFilter.eventType() == QContactChangeLogFilter::EventChanged) { // Changed since
225             variable.property<nie::contentLastModified>() >= LiteralValue(clFilter.since().toString(Qt::ISODate));
226         }
227     } else if (filter.type() == QContactFilter::UnionFilter) {
228         const QContactUnionFilter unionFilter(filter);
229         foreach (QContactFilter f, unionFilter.filters()) {
230             QContactManager::Error error = applyFilterToContact(variable, f);
231             if (QContactManager::NoError != error)
232                 return error;
233         }
234     }
235     else if(filter.type() == QContactFilter::InvalidFilter || filter.type() == QContactFilter::DefaultFilter)
236         return QContactManager::NoError;
237     else
238         return QContactManager::NotSupportedError;
239     return QContactManager::NoError;
240 }
241
242 //!\sa applyFilterToContact
243 QContactManager::Error QTrackerContactFetchRequest::applyDetailRangeFilterToContact(RDFVariable &variable, const QContactFilter &filter)
244 {
245     Q_ASSERT(filter.type() == QContactFilter::ContactDetailRangeFilter);
246     if (filter.type() == QContactFilter::ContactDetailRangeFilter) {
247         QContactDetailRangeFilter filt = filter;
248         // birthday range
249         if (QContactBirthday::DefinitionName == filt.detailDefinitionName()
250                 && QContactBirthday::FieldBirthday == filt.detailFieldName())
251         {
252             if (filt.rangeFlags() & QContactDetailRangeFilter::IncludeUpper)
253                 variable.property<nco::birthDate>() <= LiteralValue(filt.maxValue().toDate().toString(Qt::ISODate));
254             else
255                 variable.property<nco::birthDate>() < LiteralValue(filt.maxValue().toDate().toString(Qt::ISODate));
256             if (filt.rangeFlags() & QContactDetailRangeFilter::ExcludeLower)
257                 variable.property<nco::birthDate>() > LiteralValue(filt.minValue().toDate().toString(Qt::ISODate));
258             else
259                 variable.property<nco::birthDate>() >= LiteralValue(filt.minValue().toDate().toString(Qt::ISODate));
260             return QContactManager::NoError;
261         }
262     }
263     qWarning() << __PRETTY_FUNCTION__ << "Unsupported detail range filter";
264     return QContactManager::NotSupportedError;
265 }
266
267
268 /*
269  * To understand why all the following methods have for affiliation param, check nco ontology:
270  * every contact has all these properties and also linked to affiliations (also contacts - nco:Role)
271  * that again have the same properties. So it was needed to make the same query 2-ce - once for contact
272  * and once for affiliations
273  */
274 RDFSelect preparePhoneNumbersQuery(RDFVariable &rdfcontact1, bool forAffiliations)
275 {
276     RDFVariable phone;
277     if (!forAffiliations)
278         phone = rdfcontact1.property<nco::hasPhoneNumber>();
279     else
280         phone = rdfcontact1.property<nco::hasAffiliation>().property<nco::hasPhoneNumber>();
281     RDFVariable type = phone.type();
282     type.property<rdfs::subClassOf>().notEqual(nco::ContactMedium::iri()); // sparql cannot handle exact type but returns all super types as junk rows
283     type.property<rdfs::subClassOf>().notEqual(rdfs::Resource::iri()); // sparql cannot handle exact type but returns all super types as junk rows
284     // therefore we eliminate those rows that are not of interest
285     // columns
286     RDFSelect queryidsnumbers;
287     queryidsnumbers.addColumn("contactId", rdfcontact1.property<nco::contactUID> ());
288     queryidsnumbers.addColumn("phoneno", phone.property<nco::phoneNumber> ());
289     queryidsnumbers.addColumn("type", type);
290     queryidsnumbers.distinct();
291     return queryidsnumbers;
292 }
293
294 RDFSelect prepareEmailAddressesQuery(RDFVariable &rdfcontact1, bool forAffiliations)
295 {
296     RDFVariable email;
297     if (!forAffiliations)
298         email = rdfcontact1.property<nco::hasEmailAddress>();
299     else
300         email = rdfcontact1.property<nco::hasAffiliation>().property<nco::hasEmailAddress>();
301     const RDFVariable& type = email.type();
302     type.property<rdfs::subClassOf>().notEqual(nco::Resource::iri()); // sparql cannot handle exact type but returns all super types as junk rows
303     // therefore we eliminate those rows that are not of interest
304     // columns
305     RDFSelect queryidsnumbers;
306     queryidsnumbers.addColumn("contactId", rdfcontact1.property<nco::contactUID> ());
307     queryidsnumbers.addColumn("emailaddress", email.property<nco::emailAddress> ());
308     rdfcontact1.property<nco::hasEmailAddress> ().isOfType( nco::EmailAddress::iri(), true);
309     queryidsnumbers.addColumn("type", type);
310     queryidsnumbers.distinct();
311     return queryidsnumbers;
312 }
313
314 RDFSelect prepareIMContactsQuery(RDFVariable  &imcontact )
315 {
316     // columns
317     RDFSelect queryidsimacccounts;
318     imcontact = queryidsimacccounts.newColumn<nco::IMContact>("contact");
319     queryidsimacccounts.groupBy(imcontact);
320     queryidsimacccounts.addColumn("contactId", imcontact.property<nco::contactUID> ());
321
322     queryidsimacccounts.addColumn("IMId", imcontact.property<nco::imContactId> ());
323     queryidsimacccounts.addColumn("status", imcontact.optional().property<nco::imContactPresence> ());
324     queryidsimacccounts.addColumn("message", imcontact.optional().property<nco::imContactStatusMessage> ());
325     queryidsimacccounts.addColumn("nick", imcontact.optional().property<nco::imContactNickname> ());
326     queryidsimacccounts.addColumn("type", imcontact.optional().property<nco::fromIMAccount> ());
327     queryidsimacccounts.addColumn("capabilities",
328                 imcontact.optional().property<nco::imContactCapability>().filter("GROUP_CONCAT", LiteralValue(",")));
329     queryidsimacccounts.addColumn("metacontact", imcontact.optional().property<nco::metacontact> ());
330     queryidsimacccounts.addColumn("serviceprovider", imcontact.optional().property<nco::fromIMAccount>().property<nco::imDisplayName>());
331
332     return queryidsimacccounts;
333
334 }
335
336 RDFSelect prepareIMAccountsQuery(RDFVariable &rdfPersonContact)
337 {
338     RDFVariable imAccount;
339     imAccount = rdfPersonContact.property<nco::hasIMAccount> ();
340     RDFSelect queryidsimaccounts;
341
342     queryidsimaccounts.addColumn("protocol", imAccount.property<nco::imID> ());
343     queryidsimaccounts.addColumn("presence",imAccount.optional().property<nco::imPresence> ());
344     queryidsimaccounts.addColumn("message", imAccount.optional().property<nco::imStatusMessage> ());
345     queryidsimaccounts.addColumn("nick", imAccount.optional().property<nco::imNickname> ());
346     queryidsimaccounts.addColumn("displayname", imAccount.optional().property<nco::imDisplayName> ());
347
348     return queryidsimaccounts;
349 }
350
351
352 QTrackerContactAsyncRequest::QTrackerContactAsyncRequest(QContactAbstractRequest* request)
353 : req(request)
354 {
355 }
356
357 const QString rdfPhoneType2QContactSubtype(const QString rdfPhoneType)
358 {
359     if( rdfPhoneType.endsWith("VoicePhoneNumber") )
360         return QContactPhoneNumber::SubTypeVoice;
361     else if ( rdfPhoneType.endsWith("CarPhoneNumber") )
362         return QContactPhoneNumber::SubTypeCar;
363     else if ( rdfPhoneType.endsWith("CellPhoneNumber") )
364         return QContactPhoneNumber::SubTypeMobile;
365     else if ( rdfPhoneType.endsWith("BbsPhoneNumber") )
366         return QContactPhoneNumber::SubTypeBulletinBoardSystem;
367     else if ( rdfPhoneType.endsWith("FaxNumber") )
368         return QContactPhoneNumber::SubTypeFacsimile;
369     else if ( rdfPhoneType.endsWith("ModemNumber") )
370         return QContactPhoneNumber::SubTypeModem;
371     else if ( rdfPhoneType.endsWith("PagerNumber") )
372         return QContactPhoneNumber::SubTypePager;
373     else if ( rdfPhoneType.endsWith("MessagingNumber") )
374         return QContactPhoneNumber::SubTypeMessagingCapable;
375     else
376         qWarning() << Q_FUNC_INFO << "Not handled phone number type:" << rdfPhoneType;
377     return "";
378 }
379
380 QTrackerContactAsyncRequest::~QTrackerContactAsyncRequest()
381 {
382
383 }
384
385 /*!
386  * The method was initially created to add default fields in case client did not supply
387  * fields constraint - in that case the constraint is that default contact fields (ones
388  * being edited in contact card and synchronized) are queried.
389  */
390 void QTrackerContactFetchRequest::validateRequest()
391 {
392     Q_ASSERT(req);
393     Q_ASSERT(req->type() == QContactAbstractRequest::ContactFetchRequest);
394     QContactFetchRequest* r = qobject_cast<QContactFetchRequest*> (req);
395     if (r && r->definitionRestrictions().isEmpty()) {
396         QStringList fields;
397         fields << QContactAvatar::DefinitionName
398             << QContactBirthday::DefinitionName
399             << QContactAddress::DefinitionName
400             << QContactEmailAddress::DefinitionName
401             << QContactGender::DefinitionName
402             << QContactAnniversary::DefinitionName
403             << QContactName::DefinitionName
404             << QContactOnlineAccount::DefinitionName
405             << QContactOrganization::DefinitionName
406             << QContactPhoneNumber::DefinitionName
407             << QContactUrl::DefinitionName;
408         r->setDefinitionRestrictions(fields);
409     }
410 }
411
412 QTrackerContactFetchRequest::QTrackerContactFetchRequest(QContactAbstractRequest* request,
413                                                          QContactManagerEngine* parent) :
414     QObject(parent),QTrackerContactAsyncRequest(request),
415     queryPhoneNumbersNodesPending(0),
416     queryEmailAddressNodesPending(0)
417 {
418     Q_ASSERT(parent);
419     Q_ASSERT(request);
420     QContactManagerEngine::updateRequestState(req, QContactAbstractRequest::ActiveState);
421
422     QTimer::singleShot(0, this, SLOT(run()));
423 }
424
425 void QTrackerContactFetchRequest::run()
426 {
427     validateRequest();
428     QContactFetchRequest* r = qobject_cast<QContactFetchRequest*> (req);
429
430     RDFVariable RDFContact = RDFVariable::fromType<nco::PersonContact>();
431     QContactManager::Error error = applyFilterToContact(RDFContact, r->filter());
432     if (error != QContactManager::NoError)
433     {
434         emitFinished(error);
435         return;
436     }
437     if (r->definitionRestrictions().contains(QContactPhoneNumber::DefinitionName)) {
438         queryPhoneNumbersNodes.clear();
439         queryPhoneNumbersNodesPending = 2;
440         for(int forAffiliations = 0; forAffiliations <= 1; forAffiliations++) {
441             // prepare query to get all phone numbers
442             RDFVariable rdfcontact1 = RDFVariable::fromType<nco::PersonContact>();
443             applyFilterToContact(rdfcontact1, r->filter());
444             // criteria - only those with phone numbers
445             RDFSelect queryidsnumbers = preparePhoneNumbersQuery(rdfcontact1, forAffiliations);
446             queryPhoneNumbersNodes << ::tracker()->modelQuery(queryidsnumbers);
447             // need to store LiveNodes in order to receive notification from model
448             QObject::connect(queryPhoneNumbersNodes[forAffiliations].model(),
449                              SIGNAL(modelUpdated()), this, SLOT(phoneNumbersReady()));
450         }
451     }
452
453     if (r->definitionRestrictions().contains(QContactEmailAddress::DefinitionName)) {
454         queryEmailAddressNodes.clear();
455         queryEmailAddressNodesPending = 2;
456         for(int forAffiliations = 0; forAffiliations <= 1; forAffiliations++) {
457             // prepare query to get all email addresses
458             RDFVariable rdfcontact1 = RDFVariable::fromType<nco::PersonContact>();
459             applyFilterToContact(rdfcontact1, r->filter());
460             // criteria - only those with email addresses
461             RDFSelect queryidsnumbers = prepareEmailAddressesQuery(rdfcontact1,forAffiliations);
462             queryEmailAddressNodes << ::tracker()->modelQuery(queryidsnumbers);
463             // need to store LiveNodes in order to receive notification from model
464             QObject::connect(queryEmailAddressNodes[forAffiliations].model(),
465                              SIGNAL(modelUpdated()), this, SLOT(emailAddressesReady()));
466         }
467     }
468
469     if ( r->definitionRestrictions().contains( QContactOnlineAccount::DefinitionName) ) {
470         queryIMAccountNodesPending = 1;
471
472         RDFSelect queryidsimaccounts;
473         RDFVariable rdfIMContact;
474         rdfIMContact = rdfIMContact.fromType<nco::IMContact> ();
475
476         if(isMeContact(r->filter())) {
477             RDFVariable rdfPersonContact;
478             rdfPersonContact = rdfPersonContact.fromType<nco::PersonContact> ();
479             // Prepare a query to get all IMAccounts from all  accounts.
480             // nco:PersonContact -- nco:hasIMAccount -- nco:IMAccount
481             queryidsimaccounts = prepareIMAccountsQuery(rdfPersonContact);
482         } else {
483             // Prepare a query to get all IMContacts from all accounts.
484             queryidsimaccounts = prepareIMContactsQuery(rdfIMContact);
485         }
486
487         if( r->filter().type() != QContactFilter::DefaultFilter )
488         {
489             // need to get all IMContacts with the same contact id (1) and all IMContacts
490             // that have the same metacontact (2)
491
492             // (1)rdfcontact1 represent the contacts that fits to the filter
493             RDFVariable rdfcontact1;
494             applyFilterToContact(rdfcontact1, r->filter());
495
496             // (2) select all contacts that have the same metacontact as some contact corresponding to given filter
497             // and if metacontact exist.
498             RDFVariable rdfcontact3;
499             rdfcontact3.property<nco::metacontact> () = rdfcontact1.optional().property<nco::metacontact> ();
500
501             // aggregated criteria - only those with im contacts that match rdfcontact1 (same id) or rdfcontact3 (same metacontact)
502             rdfIMContact.isMemberOf(RDFVariableList()<<rdfcontact1<<rdfcontact3);
503         }
504
505         queryIMAccountNodes = ::tracker()->modelQuery(queryidsimaccounts);
506         QObject::connect(queryIMAccountNodes.model(),
507                 SIGNAL(modelUpdated()), SLOT(iMAcountsReady()));
508     }
509
510     QList<QContactLocalId> ids;
511     RDFVariable RDFContact1 = RDFVariable::fromType<nco::PersonContact>();
512     applyFilterToContact(RDFContact1, r->filter());
513     RDFSelect quer;
514     RDFVariable prefix = RDFContact1.optional().property<nco::nameHonorificPrefix> ();
515     RDFVariable lastname = RDFContact1.optional().property<nco::nameFamily> ();
516     RDFVariable middlename = RDFContact1.optional().property<nco::nameAdditional> ();
517     RDFVariable firstname = RDFContact1.optional().property<nco::nameGiven> ();
518     RDFVariable nickname = RDFContact1.optional().property<nco::nickname> ();
519     quer.addColumn("contactId", RDFContact1.property<nco::contactUID> ());
520     quer.addColumn("metacontact",RDFContact1.optional().property<nco::metacontact> ());
521     quer.addColumn("prefix", prefix);
522     quer.addColumn("firstname", firstname);
523     quer.addColumn("middlename", middlename);
524     quer.addColumn("secondname", lastname);
525     quer.addColumn("photo", RDFContact1.optional().property<nco::photo> ());
526     quer.addColumn("nickname", nickname);
527
528     // for now adding columns to main query. later separate queries
529     if (r->definitionRestrictions().contains(QContactAddress::DefinitionName)) {
530         RDFVariable address = RDFContact.optional().property< nco::hasPostalAddress> ();
531         quer.addColumn("street",address.optional().property<nco::streetAddress> ());
532         quer.addColumn("city", address.optional().property<nco::locality> ());
533         quer.addColumn("country", address.optional().property<nco::country> ());
534         quer.addColumn("pcode", address.optional().property<nco::postalcode> ());
535         quer.addColumn("reg", address.optional().property<nco::region> ());
536     }
537     if (r->definitionRestrictions().contains(QContactUrl::DefinitionName)) {
538         quer.addColumn("homepage", RDFContact.optional().property<nco::websiteUrl> ());
539         quer.addColumn("url", RDFContact.optional().property<nco::url> ());
540         quer.addColumn("work_homepage", RDFContact.optional().property<nco::hasAffiliation> ().property<nco::websiteUrl> ());
541         quer.addColumn("work_url", RDFContact.optional().property<nco::hasAffiliation> ().property<nco::url> ());
542     }
543     if (r->definitionRestrictions().contains(QContactBirthday::DefinitionName)) {
544         quer.addColumn("birth",RDFContact.optional().property<nco::birthDate> ());
545     }
546     if (r->definitionRestrictions().contains(QContactGender::DefinitionName)) {
547         quer.addColumn("gender", RDFContact.optional().property<nco::gender> ());
548     }
549     if (r->definitionRestrictions().contains(QContactOrganization::DefinitionName)) {
550         RDFVariable rdforg = RDFContact.optional().property<nco::hasAffiliation> ().optional().property<nco::org> ();
551         quer.addColumn("org", rdforg.optional().property<nco::fullname> ());
552         quer.addColumn("logo", rdforg.optional().property<nco::logo> ());
553     }
554
555
556     // QContactAnniversary - no such thing in tracker
557     // QContactGeolocation - nco:hasLocation is not having class defined in nco yet. no properties. maybe rdfs:Resource:label
558
559     // supporting sorting only here, difficult and no requirements in UI for sorting in multivalue details (phones, emails)
560     foreach(QContactSortOrder sort, r->sorting()) {
561         if (sort.detailDefinitionName() == QContactName::DefinitionName) {
562             if (sort.detailFieldName() == QContactName::FieldFirst)
563                 quer.orderBy(firstname);
564             else if (sort.detailFieldName() == QContactName::FieldLast)
565                 quer.orderBy(lastname);
566             else
567                 qWarning() << "QTrackerContactFetchRequest" << "sorting by"
568                     << sort.detailDefinitionName()
569                     << sort.detailFieldName() << "is not yet supported";
570         } else {
571             qWarning() << "QTrackerContactFetchRequest" << "sorting by"
572                 << sort.detailDefinitionName()
573                 << "is not yet supported";
574         }
575     }
576     query = ::tracker()->modelQuery(quer);
577     // need to store LiveNodes in order to receive notification from model
578     QObject::connect(query.model(), SIGNAL(modelUpdated()), this, SLOT(contactsReady()));
579 }
580
581 bool detailExisting(const QString &definitionName, const QContact &contact, const QContactDetail &adetail)
582 {
583     QList<QContactDetail> details = contact.details(definitionName);
584     foreach(const QContactDetail &detail, details) {
585         if (detail == adetail) {
586             return true;
587         }
588     }
589     return false;
590 }
591
592 void QTrackerContactFetchRequest::contactsReady()
593 {
594     // 1) process IMContacts: queryIMAccountNodes
595     // 2) process contacts: query and during it handle metacontacts
596     // 3) process phonenumbers: queryPhoneNumbersNodes
597     // 4) process emails: queryPhoneNumbersNodes
598     // 5) update display label details
599
600     QContactFetchRequest* request = qobject_cast<QContactFetchRequest*> (req);
601     Q_ASSERT( request ); // signal is supposed to be used only for contact fetch
602     // fastest way to get this working. refactor
603     if (!request) {
604         QContactManagerEngine::updateRequestState(req, QContactAbstractRequest::FinishedState);
605         return;
606     }
607
608    /*
609     * 1) process IMContacts: queryIMAccountNodes
610     * Processing IMContacts need to be called before processing PersonContacts - \sa result is filled with IMContacts first,
611     * then metacontacts are processed while processing addressbook contacts. This is because QContactOnlineAccount details
612     * need to be constructed before linking contacts with same metacontact.
613     */
614     if (request->definitionRestrictions().contains(QContactOnlineAccount::DefinitionName)) {
615         processQueryIMContacts(queryIMAccountNodes);
616     }
617
618     // 2) process contacts: query and during it handle metacontacts
619     for(int i = 0; i < query->rowCount(); i++) {
620         QContact contact; // one we will be filling with this row
621
622         bool ok;
623         QContactLocalId contactid = query->index(i, 0).data().toUInt(&ok);
624         if (!ok) {
625             qWarning()<< Q_FUNC_INFO <<"Invalid contact ID: "<< query->index(i, 0).data().toString();
626             continue;
627         }
628         QContactId id; id.setLocalId(contactid);
629
630         QContactManagerEngine *engine = qobject_cast<QContactManagerEngine *>(parent());
631         Q_ASSERT(engine);
632         if(engine)
633             id.setManagerUri(engine->managerUri());
634
635         contact.setId(id);
636         QString metacontact = query->index(i, 1).data().toString();
637
638         // using redundancy to get less queries to tracker - it is possible
639         // that rows are repeating: information for one contact is in several rows
640         // that's why we update existing contact and append details to it if they
641         // are not already in QContact
642         QHash<quint32, int>::const_iterator it = id2ContactLookup.find(contactid);
643         //
644         int index = result.size(); // where to place new contact
645         if (id2ContactLookup.end() != it) {
646             if (it.value() < result.size() && it.value() >= 0) {
647                 index = it.value();
648                 contact = result[index];
649             }
650             Q_ASSERT(query->index(i, 0).data().toUInt() == contact.localId());
651         }
652
653         readFromQueryRowToContact(contact ,i);
654         addContactToResultSet(contact, metacontact);
655     }
656
657     // 3) process phonenumbers: queryPhoneNumbersNodes
658     if (request->definitionRestrictions().contains(QContactPhoneNumber::DefinitionName)) {
659         Q_ASSERT(queryPhoneNumbersNodes.size() == 2);
660         for( int cnt = 0; cnt < queryPhoneNumbersNodes.size(); cnt++) {
661             processQueryPhoneNumbers(queryPhoneNumbersNodes[cnt], cnt);
662         }
663     }
664
665     // 4) process emails: queryPhoneNumbersNodes
666     if (request->definitionRestrictions().contains(QContactEmailAddress::DefinitionName)) {
667         qDebug() << "processQueryEmailAddresses";
668         Q_ASSERT(queryEmailAddressNodes.size() == 2);
669         for (int cnt = 0; cnt < queryEmailAddressNodes.size(); cnt++) {
670             processQueryEmailAddresses(queryEmailAddressNodes[cnt], cnt);
671         }
672     }
673
674     // 5) update display labels
675     QContactManagerEngine *engine = dynamic_cast<QContactManagerEngine*>(parent());
676     Q_ASSERT(engine);
677     for(int i = 0; i < result.count(); i++)
678     {
679         QContact &cont(result[i]);
680         QContactDisplayLabel dl = cont.detail(QContactDisplayLabel::DefinitionName);
681         if (dl.label().isEmpty()) {
682             QContactManager::Error synthError;
683             result[i] = engine->setContactDisplayLabel(engine->synthesizedDisplayLabel(cont, synthError), cont);
684         }
685     }
686     emitFinished();
687 }
688
689 void QTrackerContactFetchRequest::emitFinished(QContactManager::Error error)
690 {
691     QContactFetchRequest *fetchRequest = qobject_cast<QContactFetchRequest *>(req);
692     Q_ASSERT(fetchRequest);
693     if(fetchRequest) {
694         QContactManagerEngine::updateRequestState(fetchRequest, QContactAbstractRequest::FinishedState);
695         QContactManagerEngine::updateContactFetchRequest(fetchRequest, result, error);
696     }
697 }
698
699 /*!
700  *  Appending contact to \sa result, id2ContactLookup,  of the request - during the operation it is resolved if to
701  *  link (merge) contact with existing one in result, to add it as new or to replace existing in result.
702  */
703 void QTrackerContactFetchRequest::addContactToResultSet(QContact &contact, const QString &metacontact)
704 {
705     QHash<quint32, int>::const_iterator it = id2ContactLookup.find(contact.localId());
706     int index = -1; // where to place new contact, -1 - not defined
707     if (id2ContactLookup.end() != it) {
708         if (it.value() < result.size() && it.value() >= 0)
709             index = it.value();
710     }
711     QContact *contact_ = &contact;
712     if( -1 == index && !metacontact.isEmpty() ) {
713         if( metacontactLookup.contains(metacontact) )
714         {
715            // we'll link them and update contact on existing position
716            index = metacontactLookup[metacontact];
717            contact_ = &(linkContactsWithSameMetaContact(contact, result[index]));
718
719         }
720     }
721
722     if ( -1 == index ) {
723         result.append(*contact_);
724         id2ContactLookup[contact_->localId()] = result.size()-1;
725         if( !metacontact.isEmpty())
726         {
727             metacontactLookup[metacontact] = result.size()-1;
728         }
729     } else {
730         result[index] = *contact_;
731         id2ContactLookup[contact_->localId()] = index;
732     }
733 }
734
735 /*!
736  * brief Processes one query record-row during read from tracker to QContact.
737  * Order or columns in query is fixed to order defined in \sa run()
738  */
739 void QTrackerContactFetchRequest::readFromQueryRowToContact(QContact &contact, int i)
740 {
741     int column = 2; // 0 - for QContactLocalId, 1 for metacontact
742     QContactName name = contact.detail(QContactName::DefinitionName);
743     name.setPrefix(query->index(i, column++).data().toString());
744     name.setFirstName(query->index(i, column++).data().toString());
745     name.setMiddleName(query->index(i, column++).data().toString());
746     name.setLastName(query->index(i, column++).data().toString());
747     contact.saveDetail(&name);
748
749     QContactAvatar avatar = contact.detail(QContactAvatar::DefinitionName);
750     avatar.setAvatar(query->index(i, column++).data().toString());
751     if (!avatar.avatar().isEmpty()) {
752         contact.saveDetail(&avatar);
753     }
754     QContactNickname nick = contact.detail(QContactNickname::DefinitionName);
755     nick.setNickname(query->index(i, column++).data().toString());
756     contact.saveDetail(&nick);
757
758     QContactFetchRequest* request = qobject_cast<QContactFetchRequest*> (req);
759     Q_ASSERT( request ); // this is handled already in caller
760     if( !request )
761         return;
762     // TODO extract generic from bellow ... mapping field names
763     if (request->definitionRestrictions().contains(QContactAddress::DefinitionName)) {
764         QString street = query->index(i, column++).data().toString();
765         QString loc = query->index(i, column++).data().toString();
766         QString country = query->index(i, column++).data().toString();
767         QString pcode = query->index(i, column++).data().toString();
768         QString region = query->index(i, column++).data().toString();
769         if (!(country.isEmpty() && pcode.isEmpty() && region.isEmpty()
770               && street.isEmpty() && loc.isEmpty())) {
771             // for multivalue fields is a bit tricky - try to find existing an
772             QContactAddress a;
773             a.setStreet(street);
774             a.setLocality(loc);
775             a.setCountry(country);
776             a.setPostcode(pcode);
777             a.setRegion(region);
778             if (!detailExisting(QContactAddress::DefinitionName, contact, a)) {
779                 contact.saveDetail(&a);
780             }
781         }
782     }
783     if (request->definitionRestrictions().contains(QContactUrl::DefinitionName)) {
784         // check query preparation (at the moment in constructor TODO refactor)
785         // home website
786         // if it is websiteUrl then interpret as homepage, if it is nco:url then fovourite url
787         QContactUrl url;
788         url.setSubType(QContactUrl::SubTypeHomePage);
789         url.setContexts(QContactUrl::ContextHome);
790         url.setUrl(query->index(i, column++).data().toString());
791         if (url.url().isEmpty()) {
792             // website url is at the same time url, so we handle duplication here
793             // if only url then set it as favourite
794             url.setUrl(query->index(i, column++).data().toString());
795             url.setSubType(QContactUrl::SubTypeFavourite);
796         }
797
798         if (!url.url().isEmpty() && !detailExisting(QContactUrl::DefinitionName, contact, url)) {
799             contact.saveDetail(&url);
800         }
801         // office website
802         QContactUrl workurl;
803         workurl.setContexts(QContactUrl::ContextWork);
804         workurl.setSubType(QContactUrl::SubTypeHomePage);
805         workurl.setUrl(query->index(i, column++).data().toString());
806         if (workurl.url().isEmpty()) {
807             workurl.setUrl(query->index(i, column++).data().toString());
808             workurl.setSubType(QContactUrl::SubTypeFavourite);
809         }
810         if (!workurl.url().isEmpty() && !detailExisting(QContactUrl::DefinitionName, contact, workurl)) {
811             contact.saveDetail(&workurl);
812         }
813     }
814     if (request->definitionRestrictions().contains(QContactBirthday::DefinitionName)) {
815         QVariant var = query->index(i, column++).data();
816         if (!var.toString().isEmpty() /* enable reading wrong && var.toDate().isValid()*/) {
817             QContactBirthday birth = contact.detail(QContactBirthday::DefinitionName);
818             birth.setDate(var.toDate());
819             contact.saveDetail(&birth);
820         }
821     }
822     if (request->definitionRestrictions().contains(QContactGender::DefinitionName)) {
823         QString var = query->index(i, column++).data().toString();
824         if (!var.isEmpty()) {
825             QContactGender g = contact.detail(QContactGender::DefinitionName);
826             g.setGender(var);
827             contact.saveDetail(&g);
828         }
829     }
830     if (request->definitionRestrictions().contains(QContactOrganization::DefinitionName)) {
831         QString org = query->index(i, column++).data().toString();
832         QString logo = query->index(i, column++).data().toString();
833         if (!( org.isEmpty() && logo.isEmpty())) {
834             QContactOrganization o;
835             o.setName(org);
836             o.setLogo(logo);
837             if (!detailExisting(QContactOrganization::DefinitionName, contact, o)) {
838                 contact.saveDetail(&o);
839             }
840         }
841     }
842
843 }
844
845 /*!
846  * When 2 contacts have the same metacontact, decide which of these 2 contacts needs
847  * to be returned as addressbook contact - the other one is void from returned set
848  * Info about linking stored in returned contact - reference to either first or second
849  */
850 QContact &QTrackerContactFetchRequest::linkContactsWithSameMetaContact(QContact &first, QContact &second)
851 {
852     bool returnFirst = true;
853     // 1) resolve which one to return as metacontact(mastercontact) contact
854     // 2) insert link the one not returned
855     // now we only merge IMContacts to addressbook contacts - if that change, changing this too
856     // check if there is existence of previous merging information or online account info
857     QList<QContactDetail> details = first.details(QContactOnlineAccount::DefinitionName);
858
859     // 1) resolve which one is to be returned and which one linked from it
860     bool allreadyContainsLinkingInfo(false);
861     foreach (QContactDetail detail, details)
862     {
863         if( !detail.value(FieldQContactLocalId).isEmpty())
864         {
865             allreadyContainsLinkingInfo = true;
866             break;
867         }
868         else if( !detail.value(FieldAccountPath).isEmpty() || !detail.value(QContactOnlineAccount::FieldPresence).isEmpty() )
869         {
870             returnFirst = false;
871             break;
872         }
873     }
874     QContact *returned, *linked;
875     if( returnFirst )
876     {
877         returned = &first;
878         linked = &second;
879     }
880     else
881     {
882         returned = &second;
883         linked = &first;
884     }
885
886     // 2) now insert linking information to returned contact
887     details = linked->details(QContactOnlineAccount::DefinitionName);
888
889     foreach (QContactDetail detail, details)
890     {
891         detail.setValue(FieldQContactLocalId, linked->localId());
892         returned->saveDetail(&detail);
893     }
894     return *returned;
895 }
896
897
898 void QTrackerContactFetchRequest::phoneNumbersReady()
899 {
900     queryPhoneNumbersNodesPending--;
901 }
902
903 void QTrackerContactFetchRequest::emailAddressesReady()
904 {
905     queryEmailAddressNodesPending--;
906 }
907
908 void QTrackerContactFetchRequest::iMAcountsReady()
909 {
910     queryIMAccountNodesPending--;
911     // now we know that the query is ready before get all contacts, check how it works with transactions
912 }
913
914 /*!
915  * An internal helper method for converting nco:PhoneNumber subtype to
916  * QContactPhoneNumber:: subtype attribute
917  */
918 void QTrackerContactFetchRequest::processQueryPhoneNumbers(SopranoLive::LiveNodes queryPhoneNumbers,
919                                                            bool affiliationNumbers )
920 {
921     Q_ASSERT_X( queryPhoneNumbersNodesPending==0, Q_FUNC_INFO, "Phonenumbers query was supposed to be ready and it is not." );
922     for (int i = 0; i < queryPhoneNumbers->rowCount(); i++) {
923         // ignore if next one is the same - asked iridian about making query to ignore supertypes
924         // TODO remove after his answer
925         if ( i-1 >= 0
926              && (queryPhoneNumbers->index(i, 0).data().toString()
927                  == queryPhoneNumbers->index(i-1, 0).data().toString())
928              && (queryPhoneNumbers->index(i, 1).data().toString()
929                  == queryPhoneNumbers->index(i-1, 1).data().toString())) {
930             // this is for ignoring duplicates. bad approach, asked iridian about
931             // how to eliminate super types in query results
932             continue;
933         }
934
935         QString subtype = rdfPhoneType2QContactSubtype(queryPhoneNumbers->index(i, 2).data().toString());
936         QContactLocalId contactid = queryPhoneNumbers->index(i, 0).data().toUInt();
937
938         QHash<quint32, int>::const_iterator it = id2ContactLookup.find(contactid);
939         if (it != id2ContactLookup.end() && it.key() == contactid && it.value() >= 0 && it.value() < result.size())
940         {
941             QContactPhoneNumber number;
942             if( affiliationNumbers )
943                 number.setContexts(QContactPhoneNumber::ContextWork);
944             else
945                 number.setContexts(QContactPhoneNumber::ContextHome);
946             number.setNumber(queryPhoneNumbers->index(i, 1).data().toString());
947             number.setSubTypes(subtype);
948             result[it.value()].saveDetail(&number);
949         }
950         else
951             Q_ASSERT(false);
952     }
953 }
954
955 void QTrackerContactFetchRequest::processQueryEmailAddresses( SopranoLive::LiveNodes queryEmailAddresses,
956                                                               bool affiliationEmails)
957 {
958     Q_ASSERT_X(queryEmailAddressNodesPending == 0, Q_FUNC_INFO, "Email query was supposed to be ready and it is not." );
959     for (int i = 0; i < queryEmailAddresses->rowCount(); i++) {
960         // ignore if next one is the same - asked iridian about making query to ignore supertypes
961         // TODO remove after his answer
962         if ( i-1 >= 0
963              && (queryEmailAddresses->index(i, 0).data().toString()
964                  == queryEmailAddresses->index(i-1, 0).data().toString())
965              && (queryEmailAddresses->index(i, 1).data().toString()
966                  == queryEmailAddresses->index(i-1, 1).data().toString())) {
967             // this is for ignoring duplicates. bad approach, asked iridian
968             // about how to eliminate super types in query results
969             continue;
970         }
971
972         //QString subtype = rdfPhoneType2QContactSubtype(queryEmailAddresses->index(i, 2).data().toString());
973         QContactLocalId contactid = queryEmailAddresses->index(i, 0).data().toUInt();
974
975         QHash<quint32, int>::const_iterator it = id2ContactLookup.find(contactid);
976         if (it != id2ContactLookup.end() && it.key() == contactid && it.value() >= 0 && it.value() < result.size())
977         {
978             QContactEmailAddress email;
979             if (affiliationEmails)
980                 email.setContexts(QContactEmailAddress::ContextWork);
981             else
982                 email.setContexts(QContactEmailAddress::ContextHome);
983             email.setEmailAddress(queryEmailAddresses->index(i, 1).data().toString());
984             //email.setSubTypes(subtype);
985             result[it.value()].saveDetail(&email);
986         }
987         else
988             Q_ASSERT(false);
989     }
990 }
991
992
993 /*!
994  * \brief Processes one query record-row during read from tracker to QContactOnlineAccount.
995  * Order or columns in query is fixed to order defined in \sa prepareIMContactsQuery()
996  */
997 QContactOnlineAccount QTrackerContactFetchRequest::getOnlineAccountFromIMQuery(LiveNodes imAccountQuery, int queryRow)
998 {
999     QContactOnlineAccount account;
1000     QContactFetchRequest* r = qobject_cast<QContactFetchRequest*> (req);
1001     if(isMeContact(r->filter())) {
1002         account = getIMAccountFromIMQuery(imAccountQuery, queryRow);
1003     } else {
1004         account = getIMContactFromIMQuery(imAccountQuery, queryRow);
1005     }
1006     return account;
1007 }
1008
1009 /*!
1010  * \brief processes IMQuery results. \sa prepareIMContactsQuery, contactsReady
1011  * Note: in contactsReady(), this method is called before processing PersonContacts - \sa result is filled with IMContacts first,
1012  * then metacontacts are processed while processing addressbook contacts - this is because QContactOnlineAccount details
1013  * need to be constructed before linking contacts with same metacontact.
1014  */
1015 void QTrackerContactFetchRequest::processQueryIMContacts(SopranoLive::LiveNodes queryIMContacts)
1016 {
1017     Q_ASSERT_X(queryIMAccountNodesPending == 0, Q_FUNC_INFO, "IMAccount query was supposed to be ready and it is not." );
1018     for (int i = 0; i < queryIMContacts->rowCount(); i++) {
1019         QContactOnlineAccount account = getOnlineAccountFromIMQuery(queryIMContacts, i);
1020         QContactLocalId contactid = queryIMContacts->index(i, IMContact::ContactId).data().toUInt();
1021
1022         QHash<quint32, int>::const_iterator it = id2ContactLookup.find(contactid);
1023         if (it != id2ContactLookup.end() && it.key() == contactid && it.value() >= 0 && it.value() < result.size())
1024         {
1025             result[it.value()].saveDetail(&account);
1026         }
1027         else
1028         {
1029             QContact contact;
1030             QContactId id; id.setLocalId(contactid);
1031
1032             QContactManagerEngine *engine = qobject_cast<QContactManagerEngine *>(parent());
1033             Q_ASSERT(engine);
1034             if(engine)
1035                 id.setManagerUri(engine->managerUri());
1036
1037             contact.setId(id);
1038             contact.saveDetail(&account);
1039             QString metacontact = queryIMContacts->index(i, IMContact::MetaContact).data().toString(); // \sa prepareIMContactsQuery()
1040             addContactToResultSet(contact, metacontact);
1041         }
1042     }
1043 }
1044
1045 bool  QTrackerContactFetchRequest::isMeContact(const QContactFilter &filter) {
1046     if (filter.type() == QContactFilter::LocalIdFilter) {
1047          QContactManagerEngine *engine = dynamic_cast<QContactManagerEngine*>(parent());
1048          if(!engine) {
1049              qWarning() << __PRETTY_FUNCTION__ << ": Could not get QContactManager. Cannot retrieve IMAccounts for me-contact.";
1050              return false;
1051          }
1052
1053         QContactManager::Error e;
1054         QContactLocalId selfId = engine->selfContactId(e);
1055         QContactLocalIdFilter filt = filter;
1056         if (filt.ids().contains(selfId)) {
1057             return true;
1058         }
1059     }
1060     return false;
1061 }
1062
1063
1064 QContactOnlineAccount QTrackerContactFetchRequest::getIMAccountFromIMQuery(LiveNodes imAccountQuery, int queryRow) {
1065     QContactOnlineAccount account;
1066
1067     // Custom value in QContactrOnlineAccount detail to store the account path to - to determine in My Profile to ignore the ring-account.
1068     account.setValue("Account", imAccountQuery->index(queryRow, IMAccount::ContactIMId).data().toString()); // IMId
1069     // the same is supposed to be in FieldAccountUri field
1070     account.setValue(QContactOnlineAccount::FieldAccountUri, imAccountQuery->index(queryRow, IMAccount::ContactIMId).data().toString()); // IMId
1071
1072     account.setNickname(imAccountQuery->index(queryRow, IMAccount::ContactNickname).data().toString()); // nick
1073
1074     QString presence = imAccountQuery->index(queryRow, IMAccount::ContactPresence).data().toString(); // imPresence iri
1075     presence = presence.right(presence.length() - presence.lastIndexOf("presence-status"));
1076     account.setPresence(presenceConversion[presence]);
1077     qDebug() << "Presence converted: " << account.presence() << "raw presence: " << presence;
1078
1079     account.setStatusMessage(imAccountQuery->index(queryRow, IMAccount::ContactMessage).data().toString()); // imStatusMessage
1080
1081     return account;
1082 }
1083
1084 QContactOnlineAccount QTrackerContactFetchRequest::getIMContactFromIMQuery(LiveNodes imContactQuery, int queryRow) {
1085     QContactOnlineAccount account;
1086
1087     account.setValue("Account", imContactQuery->index(queryRow, IMContact::ContactIMId).data().toString()); // IMId
1088     if (!imContactQuery->index(queryRow, IMContact::AccountType).data().toString().isEmpty()) {
1089         QString accountPathURI = imContactQuery->index(queryRow, IMContact::AccountType).data().toString();
1090         QStringList decoded = accountPathURI.split(":");
1091         // taking out the prefix "telepathy:"
1092         qDebug() << __PRETTY_FUNCTION__ << decoded.value(1);
1093         account.setValue(FieldAccountPath, decoded.value(1));
1094     }
1095     account.setNickname(imContactQuery->index(queryRow, IMContact::ContactNickname).data().toString()); // nick
1096
1097     QString cap = imContactQuery->index(queryRow, IMContact::Capabilities).data().toString();
1098     cap = cap.right(cap.length() - cap.lastIndexOf("im-capability"));
1099     account.setValue(QContactOnlineAccount::FieldCapabilities, cap);
1100
1101     QString presence = imContactQuery->index(queryRow, IMContact::ContactPresence).data().toString(); // imPresence iri
1102     presence = presence.right(presence.length() - presence.lastIndexOf("presence-status"));
1103     account.setPresence(presenceConversion[presence]);
1104  
1105     account.setStatusMessage(imContactQuery->index(queryRow, IMContact::ContactMessage).data().toString()); // imStatusMessage
1106     account.setServiceProvider(imContactQuery->index(queryRow, IMContact::ServiceProvider).data().toString()); // service name
1107
1108     return account;
1109 }