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