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 } |
|