Fixes: QContactFetchRequest returns QContactOrganization detail for every contact
[qtcontacts-tracker:qtcontacts-tracker.git] / src / engine / contactfetchrequest.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt Mobility Components.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "contactfetchrequest.h"
43
44 #include "qctrequestextensions.h"
45 #include "engine.h"
46
47 #include <dao/contactdetail.h>
48 #include <dao/contactdetailschema.h>
49 #include <dao/logger.h>
50 #include <dao/querybuilder.h>
51 #include <dao/subject.h>
52 #include <dao/support.h>
53
54 using namespace SopranoLive;
55 using namespace SopranoLive::Ontologies;
56
57 ///////////////////////////////////////////////////////////////////////////////////////////////////
58
59 class QTrackerContactFetchRequest::DetailContext
60 {
61 public:
62     explicit DetailContext(const QTrackerContactDetail &definition,
63                            int firstColumn, int lastColumn, int queryOffset)
64         : m_definition(definition)
65         , m_firstColumn(firstColumn)
66         , m_lastColumn(lastColumn)
67         , m_queryOffset(queryOffset)
68     {
69     }
70
71     const QTrackerContactDetail & definition() const { return m_definition; }
72     int firstColumn() const { return m_firstColumn; }
73     int lastColumn() const { return m_lastColumn; }
74     int queryOffset() const { return m_queryOffset; }
75
76 private:
77     QTrackerContactDetail   m_definition;
78     int                     m_firstColumn;
79     int                     m_lastColumn;
80     int                     m_queryOffset;
81 };
82
83 ///////////////////////////////////////////////////////////////////////////////////////////////////
84
85 class QTrackerContactFetchRequest::QueryContext
86 {
87 public:
88     explicit QueryContext(const QTrackerContactDetailSchema &schema)
89         : m_schema(schema)
90     {
91     }
92
93 public: // attributes
94     const QTrackerContactDetailSchema & schema() const { return m_schema; }
95     const QString & contactType() const { return m_schema.contactType(); }
96
97 public: // fields
98     QList<RDFSelect> queries;
99     QList<DetailContext> details;
100     QSet<QString> customDetailHints;
101     QList<SopranoLive::LiveNodes> liveNodes;
102
103 private: // fields
104     QTrackerContactDetailSchema m_schema;
105 };
106
107 ///////////////////////////////////////////////////////////////////////////////////////////////////
108
109 class QTrackerContactDetailBuilder
110 {
111 public:
112     QTrackerContactDetailBuilder(QContactLocalId contactId, const QString &contactIri,
113                                  const QTrackerContactDetail &definition)
114         : m_contactId(contactId)
115         , m_contactIri(contactIri)
116         , m_definition(definition)
117     {
118     }
119
120     const QString & detailName() const
121     {
122         return m_definition.name();
123     }
124
125     QTrackerContactSubject::Scheme resourceIriScheme() const
126     {
127         return m_definition.resourceIriScheme();
128     }
129
130     bool hasDetailUriColumn() const
131     {
132         return m_definition.hasDetailUri();
133     }
134
135     void setResourceIri(const QString &resourceIri )
136     {
137         m_resourceIri = resourceIri ;
138     }
139
140     const QString & resourceIri() const
141     {
142         return m_resourceIri;
143     }
144
145     bool isNull() const
146     {
147         return m_detail.isNull();
148     }
149
150     bool setContexts(const QStringList &contexts)
151     {
152         return setValue(QContactDetail::FieldContext, contexts);
153     }
154
155     bool setValue(const QString &key, const QVariant &value)
156     {
157         return makeDetail()->setValue(key, value);
158     }
159
160     const QVariant value(const QString &key)
161     {
162         return makeDetail()->variantValue(key);
163     }
164
165     bool removeValue(const QString &key)
166     {
167         if (m_detail) {
168             return m_detail->removeValue(key);
169         }
170
171         return false;
172     }
173
174     bool save(QContact &contact)
175     {
176         if (m_detail) {
177             if (not m_resourceIri.isEmpty()) {
178                 QUrl detailUri;
179
180                 // Detail URI scheme is different from resource IRI scheme
181                 // for details like QContactPresence or online avatars.
182                 if (m_definition.detailUriScheme() != m_definition.resourceIriScheme()) {
183                     bool valid = false;
184                     QVariant value;
185
186                     value = QTrackerContactSubject::parseIri(m_definition.resourceIriScheme(),
187                                                              m_resourceIri, &valid);
188
189                     if (valid) {
190                         QVariantList valueList(QVariantList() << value);
191                         detailUri = QTrackerContactSubject::makeIri(m_definition.detailUriScheme(),
192                                                                     m_contactId, valueList);
193                     }
194                 }
195
196                 if (detailUri.isValid()) {
197                     m_detail->setDetailUri(detailUri.toString());
198                     m_detail->setLinkedDetailUris(m_resourceIri);
199                 } else {
200                     m_detail->setDetailUri(m_resourceIri);
201                 }
202             } else if (m_definition.hasContext()) {
203                 QString context(m_detail->contexts().first());
204                 m_detail->setDetailUri(m_contactIri + QLatin1Char('#') +
205                                       m_definition.name() + QLatin1Char('-') +
206                                       context);
207             } else {
208                 m_detail->setDetailUri(m_contactIri + QLatin1Char('#') + m_definition.name());
209             }
210
211             return contact.saveDetail(m_detail.data());
212         }
213
214         return false;
215     }
216
217 private:
218     QContactDetail * makeDetail()
219     {
220         if (m_detail.isNull()) {
221             m_detail.reset(new QContactDetail(detailName()));
222         }
223
224         return m_detail.data();
225     }
226
227 private:
228     const QContactLocalId                m_contactId;
229     const QString                        m_contactIri;
230     const QTrackerContactDetail         &m_definition;
231     QString                              m_resourceIri;
232     QScopedPointer<QContactDetail>       m_detail;
233 };
234
235 ///////////////////////////////////////////////////////////////////////////////////////////////////
236
237 QTrackerContactFetchRequest::QTrackerContactFetchRequest(QContactAbstractRequest *request,
238                                                          QContactTrackerEngine *engine,
239                                                          QObject *parent)
240     : QTrackerBaseRequest<QContactFetchRequest>(request, engine, parent)
241 {
242 }
243
244 QTrackerContactFetchRequest::~QTrackerContactFetchRequest()
245 {
246 }
247
248 RDFSelect
249 QTrackerContactFetchRequest::baseQuery(QTrackerContactQueryBuilder &queryBuilder)
250 {
251     RDFVariable contact;
252     RDFVariable contactId = contact.function<nco::contactLocalUID>();
253
254     foreach(const QUrl &classIri, queryBuilder.schema().contactClassIris()) {
255         contact.isOfType(classIri);
256     }
257
258     queryBuilder.bindFilter(m_request->filter(), contact);
259     contact.metaSetIdentifier(QLatin1String("contact"));
260
261     return (RDFSelect().
262             addColumn(QLatin1String("contact"), contact).
263             addColumn(QLatin1String("cid"), contactId));
264 }
265
266 QContactManager::Error
267 QTrackerContactFetchRequest::bindDetails(QueryContext &context, bool unique)
268 {
269     foreach(const QTrackerContactDetail &detail, context.schema().details()) {
270         if (unique != detail.isUnique() || detail.isSynthetic()) {
271             continue;
272         }
273
274         // skip details which are not needed according to the fetch hints
275         if (not m_definitionHints.isEmpty() && not m_definitionHints.contains(detail.name())) {
276             continue;
277         }
278
279         if (detail.fields().isEmpty()) {
280             if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
281                 qctWarn(QString::fromLatin1("Not implemented yet, skipping %1 detail").
282                         arg(detail.name()));
283             }
284
285             continue;
286         }
287
288         QTrackerContactQueryBuilder queryBuilder(context.schema(), engine()->managerUri());
289         const int lastQuery = context.queries.count();
290
291         // make sure the query list has enough entries
292         if (context.queries.isEmpty() || not detail.isUnique()) {
293             context.queries += baseQuery(queryBuilder);
294         }
295
296         // bind this detail to the proper query
297         RDFSelect &select = detail.isUnique() ? context.queries.first() : context.queries.last();
298         const int firstColumn = select.columns().count();
299         queryBuilder.bindFields(detail, select);
300
301         if (QContactManager::NoError != queryBuilder.error()) {
302             return context.queries.clear(), queryBuilder.error();
303         }
304
305         if (context.queries.isEmpty()) {
306             qctWarn("No queries built");
307             return QContactManager::UnspecifiedError;
308         }
309
310         const int lastColumn(select.columns().count());
311         context.details += DetailContext(detail, firstColumn, lastColumn, lastQuery);
312     }
313
314     return QContactManager::NoError;
315 }
316
317 RDFSelect
318 QTrackerContactFetchRequest::customDetailQuery(const QueryContext &context)
319 {
320     QTrackerContactQueryBuilder queryBuilder(context.schema(), engine()->managerUri());
321
322     RDFSelect select(baseQuery(queryBuilder));
323     RDFVariable &contact(select.columns().first().variable());
324
325     RDFVariable detail(contact.property<nao::hasProperty>());
326     RDFVariable detailName(detail.property<nao::propertyName>());
327
328     RDFVariable field(detail.property<nao::hasProperty>());
329     RDFVariable fieldName(field.property<nao::propertyName>());
330     RDFVariable fieldValue(field.property<nao::propertyValue>());
331
332     detailName.isMemberOf(literalVariableList(context.customDetailHints));
333     field.metaSetIdentifier(QLatin1String("customField"));
334
335     select.addColumn(QLatin1String("customDetail"), detail);
336     select.addColumn(QLatin1String("customDetailName"), detailName);
337     select.addColumn(QLatin1String("customFieldName"), fieldName);
338     select.addColumn(QLatin1String("customFieldValue"), fieldValue);
339
340     return select;
341 }
342
343 QContactManager::Error
344 QTrackerContactFetchRequest::buildQueries()
345 {
346     const QTrackerContactDetailSchemaMap &supportedSchemas = engine()->schemas();
347
348     m_definitionHints = m_request->fetchHint().detailDefinitionsHint().toSet();
349     m_queryContexts.clear();
350     m_nameOrder = qctRequestGetNameOrder(m_request);
351
352     for(QTrackerContactDetailSchemaMap::ConstIterator schema =
353         supportedSchemas.begin(); schema != supportedSchemas.end(); ++schema) {
354         m_queryContexts.append(QueryContext(*schema));
355
356         if (not m_definitionHints.isEmpty()) {
357             // Make sure the display name can be synthesized
358             engine()->ensureDisplayLabelDetails(m_definitionHints, m_nameOrder);
359
360             // Make sure all we can synthesized requested details
361             foreach(const QString &name, m_definitionHints.values()) {
362                 const QTrackerContactDetail *const detail = schema->detail(name);
363
364                 if (0 != detail) {
365                     m_definitionHints.unite(detail->dependencies());
366                 } else if (not schema->isSyntheticDetail(name)) {
367                     m_queryContexts.last().customDetailHints += name;
368                 }
369             }
370         }
371     }
372
373     for(QList<QueryContext>::Iterator context = m_queryContexts.begin();
374         context != m_queryContexts.end(); ++context) {
375         QContactManager::Error error;
376
377         // Build base and detail queries in separate steps to prevent
378         // detail queries being accidentally mixed into the base query.
379         if (QContactManager::NoError != (error = bindDetails(*context, true)) ||
380             QContactManager::NoError != (error = bindDetails(*context, false))) {
381             return error;
382         }
383
384         // Build custom detail query when needed
385         if (not context->customDetailHints.isEmpty()) {
386             context->queries += customDetailQuery(*context);
387         }
388
389         if (context->queries.isEmpty()) {
390             qctWarn("No queries built");
391             return QContactManager::UnspecifiedError;
392         }
393     }
394
395     return QContactManager::NoError;
396 }
397
398 const QList<RDFSelect> &
399 QTrackerContactFetchRequest::queries(const QString &contactType,
400                                      QContactManager::Error &error)
401 {
402     if (m_queryContexts.isEmpty()) {
403         error = buildQueries();
404     } else {
405         error = QContactManager::NoError;
406     }
407
408     foreach(const QueryContext &context, m_queryContexts) {
409         if (context.contactType() == contactType) {
410             return context.queries;
411         }
412     }
413
414     static const QList<RDFSelect> emptyResult;
415     error = QContactManager::BadArgumentError;
416     return emptyResult;
417 }
418
419 bool
420 QTrackerContactFetchRequest::start()
421 {
422     // build RDF queries
423     const QContactManager::Error error = buildQueries();
424
425     if (QContactManager::NoError != error) {
426         emitResult(error);
427         return false;
428     }
429
430     // launch the RDF queries
431     engine()->updateRequestState(m_request, QContactAbstractRequest::ActiveState);
432     RDFServicePtr rdfService = ::BackEnds::Tracker::tracker();
433
434     for(QList<QueryContext>::Iterator context =
435         m_queryContexts.begin(); context != m_queryContexts.end(); ++context) {
436         foreach(const RDFSelect &query, context->queries) {
437             if (engine()->hasDebugFlag(QContactTrackerEngine::ShowSelects)) {
438                 qDebug() << rdfService->rawQueryString(query);
439             }
440
441             context->liveNodes += rdfService->modelQuery(query);
442             LiveNodeModel *model = context->liveNodes.last().model();
443             m_pendingModels.insert(model);
444
445             connect(model, SIGNAL(modelUpdated()),
446                     SLOT(onModelUpdated()), Qt::QueuedConnection);
447             connect(model,
448                     SIGNAL(error(QString,RDFStrategyFlags,RDFStrategyFlags,QModelIndex)),
449                     SLOT(onError(QString)), Qt::QueuedConnection);
450         }
451     }
452
453     return true;
454 }
455
456 static QList<int> toIntList(const QStringList &stringList)
457 {
458     QList<int> intList;
459
460     foreach(const QString &element, stringList) {
461         intList.append(element.toInt());
462     }
463
464     return intList;
465 }
466
467 static QList<int> toIntList(const QString &string, const QChar separator = QLatin1Char(':'))
468 {
469     return toIntList(string.split(separator));
470 }
471
472 static bool toBool(const QString &string)
473 {
474     return (not string.isEmpty() &&
475             string != QLatin1String("0") &&
476             string.toLower() != QLatin1String("false"));
477 }
478
479 static int fetchSubTypes(const QTrackerContactDetailField &field,
480                          const QStringList &row, int column,
481                          QTrackerContactDetailBuilder &target)
482 {
483     QSet<QString> subTypes;
484
485     // fetch class based subtype
486     if (field.hasSubTypeClasses()) {
487         const QList<int> fetchedSubTypes(toIntList(row[column++]));
488
489         foreach(const ClassInfoBase &ci, field.subTypeClasses()) {
490             if (fetchedSubTypes.contains(ci.id())) {
491                 subTypes.insert(ci.text());
492             }
493         }
494     }
495
496     // check "isBound" column for each property based subtype
497     foreach(const PropertyInfoBase &pi, field.subTypeProperties()) {
498         bool isBound = toBool(row[column++]);
499
500         if (isBound) {
501             subTypes.insert(pi.text());
502         }
503     }
504
505     // apply default value if no subtypes could be read
506     if (subTypes.isEmpty() && field.hasDefaultValue()) {
507         switch(field.defaultValue().type()) {
508         case QVariant::String:
509             subTypes.insert(field.defaultValue().toString());
510             break;
511         case QVariant::StringList:
512             subTypes.unite(field.defaultValue().toStringList().toSet());
513             break;
514         default:
515             qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
516                     arg(field.defaultValue().typeName(), target.detailName(), field.name()));
517             break;
518         }
519     }
520
521     // set field if any subtypes could be identified
522     if (not subTypes.isEmpty()) {
523         switch(field.dataType()) {
524         case QVariant::String:
525             // TODO: clone this detail multiple times when |subTypes| > 1
526             target.setValue(field.name(), *subTypes.begin());
527             break;
528
529         case QVariant::StringList:
530             target.setValue(field.name(), QStringList(subTypes.toList()));
531             break;
532
533         default:
534             qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
535                     arg(QVariant::typeToName(field.dataType()), target.detailName(), field.name()));
536             break;
537         }
538     }
539
540     return column;
541 }
542
543 int
544 QTrackerContactFetchRequest::fetchInstances(const QTrackerContactDetailField &field,
545                                             const QStringList &row, int column,
546                                             QTrackerContactDetailBuilder &target) const
547 {
548     const QString &columnValue = row[column++];
549
550     if (columnValue.isEmpty()) {
551         return column;
552     }
553
554     // apply string list when requested
555     if (QVariant::StringList == field.dataType()) {
556         const QList<int> fetchedInstances(toIntList(columnValue));
557         QStringList instances;
558
559         foreach(const InstanceInfoBase &ii, field.allowableValues()) {
560             if (fetchedInstances.contains(ii.id())) {
561                 instances.append(ii.text());
562             }
563         }
564
565         // append default value if instances could not be identified
566         if (instances.isEmpty() && field.hasDefaultValue()) {
567             instances.append(field.defaultValue().toString());
568         }
569
570         target.setValue(field.name(), instances);
571         return column;
572     }
573
574     // otherwise apply single value
575     bool hasInstanceId = false;
576     const int instanceId = columnValue.toInt(&hasInstanceId);
577
578     if (hasInstanceId) {
579         foreach(const InstanceInfoBase &instance, field.allowableValues()) {
580             if (instance.id() == instanceId) {
581                 target.setValue(field.name(), instance.value());
582                 return column;
583             }
584         }
585     }
586
587     qctWarn(QString::fromLatin1("Unknown instance id for %2 field of %1 detail: %3").
588             arg(target.detailName()).arg(field.name()).arg(columnValue));
589
590     // apply default value if instances could not be identified
591     if (field.hasDefaultValue()) {
592         target.setValue(field.name(), field.defaultValue());
593     } else {
594         target.removeValue(field.name());
595     }
596
597     return column;
598 }
599
600 int
601 QTrackerContactFetchRequest::fetchField(const QTrackerContactDetailField &field,
602                                         const QStringList &row, int column,
603                                         QTrackerContactDetailBuilder &target) const
604 {
605     if (field.hasSubTypes()) {
606         return fetchSubTypes(field, row, column, target);
607     }
608
609     if (not field.hasPropertyChain()) {
610         return column;
611     }
612
613     if (field.restrictsValues()) {
614         return fetchInstances(field, row, column, target);
615     }
616
617     if (field.isWithoutMapping()) {
618         return column;
619     }
620
621     const QString &rawValue = row[column++];
622
623     if (rawValue.isEmpty()) {
624         return column;
625     }
626
627     QVariant parsedValue;
628
629     if (not field.parseValue(rawValue, parsedValue)) {
630         qctWarn(QString::fromLatin1("Cannot convert value to %1 for %3 field of %2 detail: %4").
631                 arg(QVariant::typeToName(field.dataType()),
632                     target.detailName(), field.name(), rawValue));
633
634         return column;
635     }
636
637     if ((QVariant::String == parsedValue.type() && parsedValue.toString().isEmpty()) ||
638         (QVariant::StringList == parsedValue.type() && parsedValue.toStringList().isEmpty())) {
639         return column;
640     }
641
642     target.setValue(field.name(), parsedValue);
643
644     return column;
645 }
646
647 int
648 QTrackerContactFetchRequest::fetchCustomValues(const QTrackerContactDetailField &field,
649                                                const QStringList &row, int column,
650                                                QTrackerContactDetailBuilder &target) const
651 {
652     switch (field.dataType()) {
653     case QVariant::StringList: {
654         QStringList values =
655                 target.value(field.name()).toStringList() +
656                 row[column++].split(QLatin1Char(':'));
657
658         values.removeAll(QString());
659         values.removeDuplicates();
660
661         target.setValue(field.name(), values);
662         break;
663     }
664
665     case QVariant::String:
666         target.setValue(field.name(), row[column++]);
667         break;
668
669     default:
670         qctWarn(QString::fromLatin1("Cannot fetch custom values for %2 field of %1 detail: "
671                                     "Data type %3 is not supported yet.").
672                 arg(target.detailName(), field.name(), QVariant::typeToName(field.dataType())));
673         break;
674     }
675
676     return column;
677 }
678
679 void
680 QTrackerContactFetchRequest::fetchBaseModel(ContactCache &results, const QueryContext &context)
681 {
682     LiveNodeModel *const model = context.liveNodes.first().model();
683
684     if (model->canFetchMore(QModelIndex())) {
685         qctWarn("Model claims to have even more rows. This should not happen.");
686     }
687
688     for(int i = 0; i < model->rowCount(); ++i) {
689         const QStringList row = model->rawRow(i);
690
691         // find local contact Id
692         bool haveLocalId = false;
693         const QString &contactIri = row[0];
694         QContactLocalId localId = row[1].toUInt(&haveLocalId);
695
696         if (not haveLocalId) {
697             localId = parseContactIri(contactIri, &haveLocalId);
698         }
699
700         if (not haveLocalId) {
701             qctWarn(QString::fromLatin1("Skipping contact without local id property "
702                                         "and with unsupported IRI: %1").arg(contactIri));
703             continue;
704         }
705
706         // create new contact
707         QContactId id;
708         id.setLocalId(localId);
709         id.setManagerUri(engine()->managerUri());
710
711         QContact contact;
712         contact.setId(id);
713         contact.setType(context.contactType());
714
715         // read unique details
716         foreach(const DetailContext &detail, context.details) {
717             if (not detail.definition().isUnique()) {
718                 continue;
719             }
720
721             QTrackerContactDetailBuilder detailBuilder(localId, contactIri, detail.definition());
722             int lastColumn = detail.firstColumn();
723
724             // fetch content URI column
725             if (detailBuilder.hasDetailUriColumn()) {
726                 detailBuilder.setResourceIri(row[lastColumn++]);
727             }
728
729             // fetch field values
730             foreach(const QTrackerContactDetailField &field, detail.definition().fields()) {
731                 if (field.hasSubTypes()) {
732                     qctWarn(QString::fromLatin1("Field %2 of unique detail %1 cannot have subtypes").
733                             arg(detail.definition().name(), field.name()));
734                     continue;
735                 }
736
737                 lastColumn = fetchField(field, row, lastColumn, detailBuilder);
738
739                 if (field.permitsCustomValues()) {
740                     lastColumn = fetchCustomValues(field, row, lastColumn, detailBuilder);
741                 }
742             }
743
744             if (lastColumn != detail.lastColumn()) {
745                 qctWarn(QString::fromLatin1("Invalid columns for %1 detail: "
746                                             "Reached column %2, but column %3 was expected.").
747                         arg(detail.definition().name()).arg(lastColumn).arg(detail.lastColumn()));
748                 continue;
749             }
750
751             // Resource IRI will be empty when a null was returned in Sparql
752             if (detailBuilder.hasDetailUriColumn() && detailBuilder.resourceIri().isEmpty()) {
753                 continue;
754             }
755
756             if (not detailBuilder.isNull() && not detailBuilder.save(contact)) {
757                 qCritical()
758                         << metaObject()->className() << "Cannot save fetched detail:"
759                         << detail.definition().name();
760             }
761         }
762
763         // store the fetched contact
764         results.insert(localId, contact);
765     }
766 }
767
768 void
769 QTrackerContactFetchRequest::fetchDetailModel(ContactCache &results,
770                                               const QueryContext &context,
771                                               const DetailContext &detail)
772 {
773     // unique details have their properties attached to the base model
774     if (detail.definition().isUnique()) {
775         return;
776     }
777
778     LiveNodeModel *const model = context.liveNodes[detail.queryOffset()].model();
779
780     if (model->canFetchMore(QModelIndex())) {
781         qctWarn("Model claims to have even more rows. This should not happen.");
782     }
783
784     const QString &detailName = detail.definition().name();
785
786     for(int i = 0; i < model->rowCount(); ++i) {
787         const QStringList row = model->rawRow(i);
788         int lastColumn = 0;
789
790         bool haveLocalId = false;
791         const QString &contactIri = row[lastColumn++];
792         QContactLocalId localId = row[lastColumn++].toUInt(&haveLocalId);
793
794         if (not haveLocalId) {
795             localId = parseContactIri(contactIri, &haveLocalId);
796         }
797
798         if (not haveLocalId) {
799             qctWarn(QString::fromLatin1("Skipping contact without local id property "
800                                         "and with unsupported IRI: %1").arg(contactIri));
801             continue;
802         }
803
804         // find the already constructed contact matching this row
805         const ContactCache::Iterator contact(results.find(localId));
806
807         if (contact == results.end()) {
808             qctWarn(QString::fromLatin1("Skipping unexpected contact: %1").arg(contactIri));
809             continue;
810         }
811
812         QTrackerContactDetailBuilder detailBuilder(localId, contactIri, detail.definition());
813
814         // read context column
815         if (detail.definition().hasContext()) {
816             QString context = qctCamelCase(row[lastColumn++]);
817
818             if (context.isEmpty()) {
819                 context = QContactDetail::ContextOther.latin1();
820             }
821
822             detailBuilder.setContexts(QStringList(context));
823         }
824
825         // read content URI column
826         if (detailBuilder.hasDetailUriColumn()) {
827             detailBuilder.setResourceIri(row[lastColumn++]);
828         }
829
830         // read all regular columns
831         foreach(const QTrackerContactDetailField &field, detail.definition().fields()) {
832             lastColumn = fetchField(field, row, lastColumn, detailBuilder);
833
834             if (field.permitsCustomValues()) {
835                 lastColumn = fetchCustomValues(field, row, lastColumn, detailBuilder);
836             }
837         }
838
839         // save updates to the contact detail
840         if (not detailBuilder.isNull()) {
841             detailBuilder.save(*contact);
842         }
843
844         // check that really all columns were read
845         if (lastColumn != model->columnCount()) {
846             qctWarn(QString::fromLatin1("Invalid columns for %1 detail: "
847                                         "Just reached column %2, but model has %3 columns.").
848                     arg(detailName).arg(lastColumn).arg(model->columnCount()));
849             continue;
850         }
851     }
852 }
853
854 void
855 QTrackerContactFetchRequest::fetchCustomDetails(ContactCache &results, const QueryContext &context)
856 {
857     typedef QHash<QString, QContactDetail> DetailHash;
858     typedef QHash<QContactLocalId, DetailHash> ContactDetailHash;
859
860     LiveNodeModel *const model(context.liveNodes.last().model());
861     ContactDetailHash details;
862
863     for(int i = 0; i < model->rowCount(); ++i) {
864         const QStringList row = model->rawRow(i);
865         bool haveLocalId = false;
866
867         const QString &contactIri = row[0];
868         QContactLocalId localId   = row[1].toUInt(&haveLocalId);
869         const QString &detailIri  = row[2];
870         const QString &detailName = row[3];
871         const QString &fieldName  = row[4];
872         const QString &fieldText  = row[5];
873
874         if (not haveLocalId) {
875             localId = parseContactIri(contactIri, &haveLocalId);
876         }
877
878         if (not haveLocalId) {
879             qctWarn(QString::fromLatin1("Skipping contact without local id property "
880                                         "and with unsupported IRI: %1").arg(contactIri));
881             continue;
882         }
883
884         // lookup existing detail, or create new one
885         DetailHash &contactDetails = details[localId];
886         DetailHash::Iterator detail = contactDetails.find(detailIri);
887
888         if (detail == contactDetails.end()) {
889             detail = contactDetails.insert(detailIri, QContactDetail(detailName));
890         }
891
892         // merge new value and potentially existing previous values
893         QVariant fieldValue = detail->variantValue(fieldName);
894
895         if (fieldValue.isNull()) {
896             fieldValue = fieldText;
897         } else {
898             fieldValue = (fieldValue.toStringList() << fieldText);
899         }
900
901         // set the computed value
902         detail->setValue(fieldName, fieldValue);
903     }
904
905     for(ContactDetailHash::Iterator ci = details.begin(); ci != details.end(); ++ci) {
906         const ContactCache::Iterator contact(results.find(ci.key()));
907
908         if (contact == results.end()) {
909             qctWarn(QString::fromLatin1("Skipping unexpected contact: %1").arg(ci.key()));
910             continue;
911         }
912
913         const QContactDetailDefinitionMap detailDefs =
914                 engine()->detailDefinitions(contact->type(), 0);
915
916         for(DetailHash::Iterator di = ci.value().begin(); di != ci.value().end(); ++di) {
917             // convert fields to expected type
918             QContactDetail &detail = di.value();
919             QVariantMap fields = detail.variantValues();
920
921             const QContactDetailFieldDefinitionMap fieldDefs =
922                     detailDefs[detail.definitionName()].fields();
923
924             for(QVariantMap::Iterator fi = fields.begin(); fi != fields.end(); ++fi) {
925                 const QContactDetailFieldDefinition fd = fieldDefs.value(fi.key());
926
927                 if (QVariant::Invalid != fd.dataType() && fi.value().convert(fd.dataType())) {
928                     detail.setValue(fi.key(), fi.value());
929                 }
930             }
931
932             // finally save the detail
933             contact->saveDetail(&detail);
934         }
935     }
936 }
937
938 void
939 QTrackerContactFetchRequest::onError(QString const &message)
940 {
941     qctWarn(QString::fromLatin1("Request failed: %1").arg(qctTruncate(message)));
942
943     while(not m_pendingModels.isEmpty()) {
944         LiveNodeModel *model(*m_pendingModels.begin());
945         m_pendingModels.erase(m_pendingModels.begin());
946         model->stopOperations();
947     }
948
949     emitResult(QContactManager::UnspecifiedError);
950 }
951
952 static void
953 updateDetailLinks(QContact &contact)
954 {
955     typedef QHash<QString, QContactDetail> DetailHash;
956
957     DetailHash detailHash;
958
959     foreach(const QContactDetail &detail, contact.details()) {
960         if (not detail.detailUri().isEmpty()) {
961             detailHash.insert(detail.detailUri(), detail);
962         }
963     }
964
965     for(DetailHash::Iterator detail = detailHash.begin(); detail != detailHash.end(); ++detail) {
966         foreach(const QString &detailUri, detail->linkedDetailUris()) {
967             DetailHash::Iterator target = detailHash.find(detailUri);
968
969             if (target == detailHash.end()) {
970                 continue;
971             }
972
973             QStringList linkedDetailUris = target->linkedDetailUris();
974
975             if (linkedDetailUris.contains(detail->detailUri())) {
976                 continue;
977             }
978
979             linkedDetailUris.append(detail->detailUri());
980             target->setLinkedDetailUris(linkedDetailUris);
981             contact.saveDetail(&target.value());
982         }
983     }
984 }
985
986 void
987 QTrackerContactFetchRequest::onModelUpdated()
988 {
989     if (m_request->isFinished()) {
990         return; // Obviously an error was reported. Let's abort quickly!
991     }
992
993     LiveNodeModel *model(qobject_cast<LiveNodeModel *>(sender()));
994
995     if (0 == model) {
996         qctWarn("Ignoring signal from invalid sender.");
997         return;
998     }
999
1000     m_pendingModels.remove(model);
1001
1002     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowModels)) {
1003         qDebug() << *model;
1004     }
1005
1006     if (not m_pendingModels.isEmpty()) {
1007         return; // what for other models to finish
1008     }
1009
1010     ContactCache results;
1011
1012     foreach(const QueryContext &context, m_queryContexts) {
1013         fetchBaseModel(results, context);
1014
1015         foreach(const DetailContext &detail, context.details) {
1016             fetchDetailModel(results, context, detail);
1017         }
1018
1019         if (not context.customDetailHints.isEmpty()) {
1020             fetchCustomDetails(results, context);
1021         }
1022     }
1023
1024     // Update synthetic details and detail links
1025     for(ContactCache::Iterator c = results.begin(); c != results.end() ; ++c) {
1026         engine()->updateGlobalPresence(c.value());
1027         engine()->updateDisplayLabel(c.value(), m_nameOrder);
1028         updateDetailLinks(c.value());
1029     }
1030
1031     // Sort the contacts
1032     const QList<QContactLocalId> sortedIds =
1033             QContactManagerEngine::sortContacts(results.values(),
1034                                                 m_request->sorting());
1035
1036     QList<QContact> contacts;
1037
1038     foreach(const QContactLocalId localId, sortedIds) {
1039         contacts.append(results.value(localId));
1040     }
1041
1042     // Report the result
1043     emitResult(QContactManager::NoError, contacts);
1044 }
1045
1046 void
1047 QTrackerContactFetchRequest::emitResult(QContactManager::Error error,
1048                                         const QList<QContact> &contacts)
1049 {
1050     engine()->updateContactFetchRequest(m_request, contacts, error,
1051                                        QContactAbstractRequest::FinishedState);
1052 }
1053
1054 ///////////////////////////////////////////////////////////////////////////////////////////////////
1055
1056 #include "moc_contactfetchrequest.cpp"