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