Changes : Make Display Lables work
[qtcontacts-tracker:qtcontacts-tracker.git] / src / engine / contactfetchrequest2.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 "contactfetchrequest2.h"
43
44 #include <dao/contactdetail.h>
45 #include <dao/contactdetailschema.h>
46 #include <dao/querybuilder.h>
47 #include <dao/subject.h>
48
49 #include <QtDebug> // FIXME
50
51 using namespace SopranoLive;
52
53 QTM_BEGIN_NAMESPACE;
54
55 ///////////////////////////////////////////////////////////////////////////////////////////////////
56
57 class QTrackerContactDetailNode {
58 public:
59     explicit QTrackerContactDetailNode(const QTrackerContactDetail &detail,
60                                        int firstColumn, int lastColumn, int queryOffset) :
61     mDetail(detail), mFirstColumn(firstColumn), mLastColumn(lastColumn),
62     mQueryOffset(queryOffset)
63     {
64     }
65
66     const QTrackerContactDetail & detail() const { return mDetail; }
67     int firstColumn() const { return mFirstColumn; }
68     int lastColumn() const { return mLastColumn; }
69     int queryOffset() const { return mQueryOffset; }
70
71 private:
72     QTrackerContactDetail   mDetail;
73     int                     mFirstColumn;
74     int                     mLastColumn;
75     int                     mQueryOffset;
76 };
77
78 ///////////////////////////////////////////////////////////////////////////////////////////////////
79
80 class QContactDetailPointer
81 {
82 public:
83     QContactDetailPointer(const QString &contactIri, const QTrackerContactDetail &definition)
84         : mContactIri(contactIri), mSubjectDomain(definition.subjectDomain()),
85         mDetailDomain(definition.detailDomain()), mDetailName(definition.name())
86     {
87     }
88
89     QContactDetail * data()
90     {
91         if (mDetail.isNull()) {
92             mDetail.reset(new QContactDetail(detailName()));
93
94             if (mSubject.isEmpty()) {
95                 mDetail->setDetailUri(mContactIri + QLatin1Char('#') + mDetailName);
96             } else {
97                 QUrl detailUri;
98
99                 if (mDetailDomain != mSubjectDomain) {
100                     bool valid = false;
101                     QVariant value;
102
103                     value = QTrackerContactSubject::parseIri(mSubjectDomain, mSubject, &valid);
104
105                     if (valid) {
106                         detailUri = QTrackerContactSubject::makeIri(mDetailDomain, value);
107                     }
108                 }
109
110                 if (detailUri.isValid()) {
111                     mDetail->setDetailUri(detailUri.toString());
112                     mDetail->setLinkedDetailUris(mSubject);
113                 } else {
114                     mDetail->setDetailUri(mSubject);
115                 }
116             }
117         }
118
119         return mDetail.data();
120     }
121
122     const QString & detailName() const
123     {
124         return mDetailName;
125     }
126
127     bool hasSubjectDomain() const
128     {
129         return QTrackerContactSubject::Anonymous != mSubjectDomain;
130     }
131
132     void setSubject(const QString &subject)
133     {
134         mSubject = subject;
135     }
136
137     const QString & subject() const
138     {
139         return mSubject;
140     }
141
142     QContactDetail * operator->()
143     {
144         return data();
145     }
146
147     bool isNull() const { return mDetail.isNull(); }
148
149 private:
150     const QString                        mContactIri;
151     const QTrackerContactSubject::Domain mSubjectDomain;
152     QString                              mSubject;
153     const QTrackerContactSubject::Domain mDetailDomain;
154     const QString                        mDetailName;
155     QScopedPointer<QContactDetail>       mDetail;
156 };
157
158 ///////////////////////////////////////////////////////////////////////////////////////////////////
159
160 QTrackerContactFetchRequest2::QTrackerContactFetchRequest2(QContactAbstractRequest *request,
161                                                            QContactTrackerEngine *engine,
162                                                            QObject *parent) :
163     QObject(parent), mEngine(engine),
164     mRequest(qobject_cast<QContactFetchRequest *>(request))
165 {
166     Q_ASSERT(0 != mEngine);
167     Q_ASSERT(0 != mRequest);
168
169     mEngine->updateRequestState(mRequest, QContactAbstractRequest::ActiveState);
170 }
171
172 QTrackerContactFetchRequest2::~QTrackerContactFetchRequest2()
173 {
174 }
175
176 QContactManager::Error
177 QTrackerContactFetchRequest2::bindDetails(bool unique)
178 {
179     const QSet<QString> definitionHints(mRequest->fetchHint().detailDefinitionsHint().toSet());
180
181     foreach(const QTrackerContactDetail &detail, mEngine->detailSchema().details()) {
182         if (unique != detail.isUnique()) {
183             continue;
184         }
185
186         // skip details which are not needed according to the fetch hints
187         if (not definitionHints.isEmpty() && not definitionHints.contains(detail.name())) {
188             continue;
189         }
190
191         if (detail.fields().isEmpty()) {
192             qWarning() << "not implemented yet, skipping: " << detail.name();
193             continue;
194         }
195
196         const int firstColumn = mQueries.isEmpty() ? 1 : mQueries.first().columns().count();
197         const int lastQuery = qMax(1, mQueries.count());
198
199         // make sure the query list has enough entries
200         if (mQueries.isEmpty() || not detail.isUnique()) {
201             const QString contactId(QLatin1String("contact"));
202             RDFVariable contact(RDFVariable::fromType<nco::PersonContact>(contactId));
203             mQueries.append(RDFSelect().addColumn(contact).orderBy(contact).distinct());
204         }
205
206         // bind this detail to the proper query
207         RDFSelect &query(detail.isUnique() ? mQueries.first() : mQueries.last());
208
209         QTrackerContactQueryBuilder queryBuilder(mEngine->detailSchema(),
210                                                  detail.isUnique() ? 0 : &detail);
211
212         queryBuilder.bindColumns(detail, query);
213         queryBuilder.bindFields(detail, mRequest->filter(), query);
214
215         if (QContactManager::NoError != queryBuilder.error()) {
216             mQueries.clear();
217             return queryBuilder.error();
218         }
219
220         Q_ASSERT(not mQueries.isEmpty());
221
222         const int lastColumn = mQueries.isEmpty() ? 1 : mQueries.first().columns().count();
223         mDetails.append(QTrackerContactDetailNode(detail, firstColumn, lastColumn, lastQuery));
224     }
225
226     return QContactManager::NoError;
227 }
228
229 QContactManager::Error
230 QTrackerContactFetchRequest2::buildQueries()
231 {
232     QContactManager::Error error(QContactManager::NoError);
233
234     mQueries.clear();
235
236     // Build base and detail queries in separate steps to prevent
237     // detail queries being accidentally mixed into the base query.
238     if (QContactManager::NoError == (error = bindDetails(true)) &&
239         QContactManager::NoError == (error = bindDetails(false))) {
240         Q_ASSERT(not mQueries.isEmpty());
241     }
242
243     return error;
244 }
245
246
247 const QList<RDFSelect> &
248 QTrackerContactFetchRequest2::queries(QContactManager::Error &error)
249 {
250     if (mQueries.isEmpty()) {
251         error = buildQueries();
252     } else {
253         error = QContactManager::NoError;
254     }
255
256     return mQueries;
257 }
258
259 void
260 QTrackerContactFetchRequest2::run()
261 {
262     // build RDF queries
263     QContactManager::Error error(buildQueries());
264
265     if (QContactManager::NoError != error) {
266         mEngine->updateContactFetchRequest(mRequest, QList<QContact>(), error,
267                                            QContactAbstractRequest::FinishedState);
268         return;
269     }
270
271     // launch the RDF queries
272     mEngine->updateRequestState(mRequest, QContactAbstractRequest::ActiveState);
273
274     foreach(const RDFSelect &query, mQueries) {
275         mLiveNodes.append(::tracker()->modelQuery(query));
276         mPending.insert(mLiveNodes.last().model());
277
278         connect(mLiveNodes.last().model(), SIGNAL(modelUpdated()),
279                 this, SLOT(modelUpdated()));
280     }
281
282     // wait for query result notifications
283     mEventLoop.reset(new QEventLoop());
284     mEventLoop->exec();
285 }
286
287 static int
288 fetchSubTypes(const QTrackerContactDetailField &field, LiveNodeModel *model,
289               int row, int column, QContactDetailPointer &target)
290 {
291     QSet<QString> subTypes;
292
293     // check "isBound" column for every subtype
294     foreach(const ResourceInfoPtr &resource, field.subTypes()) {
295         const QModelIndex index(model->index(row, column++));
296         QVariant isBound(model->data(index));
297         Q_ASSERT_X(not isBound.isNull(),
298                    qPrintable(target.detailName()), "IsBound column not found");
299
300         const bool success(isBound.convert(QVariant::Bool));
301         Q_ASSERT_X(success && QVariant::Bool == isBound.type(),
302                    qPrintable(target.detailName()), "IsBound column is not boolean");
303
304         if (isBound.toBool()) {
305             subTypes.insert(resource->text());
306         }
307     }
308
309     // apply default value if no subtypes could be read
310     if (subTypes.isEmpty() && field.hasDefaultValue()) {
311         Q_ASSERT_X(QVariant::String == field.defaultValue().type(),
312                    qPrintable(target.detailName()), field.defaultValue().typeName());
313
314         subTypes.insert(field.defaultValue().toString());
315     }
316
317     // set field if any subtypes could be identified
318     if (not subTypes.isEmpty()) {
319         switch(field.dataType()) {
320         case QVariant::String:
321             qWarning() << "TODO: handle |subTypes| > 1";
322             target->setValue(field.name(), *subTypes.begin());
323             break;
324
325         case QVariant::StringList:
326             target->setValue(field.name(), QStringList(subTypes.toList()));
327             break;
328
329         default:
330             Q_ASSERT_X(false, qPrintable(target.detailName()),
331                        QVariant::typeToName(field.dataType()));
332         }
333     }
334
335     return column;
336 }
337
338 static int
339 fetchInstances(const QTrackerContactDetailField &field, LiveNodeModel *model,
340                int row, int column, QContactDetailPointer &target)
341 {
342     QVariantList instances;
343
344     // read "IsBound" column for this field
345     const QModelIndex index(model->index(row, column++));
346     QVariant isBound(model->data(index));
347
348     Q_ASSERT_X(not isBound.isNull(),
349                qPrintable(target.detailName()), "");
350
351     const bool success(isBound.convert(QVariant::Bool));
352     Q_ASSERT_X(success && QVariant::Bool == isBound.type(),
353                qPrintable(target.detailName()), "");
354
355     // check "IsEqual" column for every instance
356     foreach(const ResourceInfoPtr &resource, field.instances()) {
357         const QModelIndex index(model->index(row, column++));
358         QVariant isEqual(model->data(index));
359
360         if (isEqual.isNull()) {
361             continue;
362         }
363
364         const bool success(isEqual.convert(QVariant::Bool));
365         Q_ASSERT_X(success && QVariant::Bool == isEqual.type(),
366                    qPrintable(target.detailName()), "");
367
368         if (isEqual.toBool()) {
369             instances.append(resource->value());
370         }
371     }
372
373     // apply default value if instances could not be identified
374     if (instances.isEmpty() && isBound.toBool() && field.hasDefaultValue()) {
375         instances.append(field.defaultValue());
376     }
377
378     // set field if any instances could be identified
379     if (not instances.isEmpty()) {
380         switch(field.dataType()) {
381         case QVariant::Int:
382         case QVariant::String:
383             Q_ASSERT_X(1 == instances.count(), qPrintable(target.detailName()),
384                        qPrintable(QString::number(instances.count())));
385             target->setValue(field.name(), instances.first());
386             break;
387
388         case QVariant::StringList:
389             target->setValue(field.name(), instances);
390             break;
391
392         default:
393             Q_ASSERT_X(false, qPrintable(target.detailName()),
394                        QVariant::typeToName(field.dataType()));
395         }
396     }
397
398     return column;
399 }
400
401 static int
402 fetchField(const QTrackerContactDetailField &field, LiveNodeModel *model,
403            int row, int column, QContactDetailPointer &target)
404
405 {
406     if (field.hasSubTypes()) {
407         return fetchSubTypes(field, model, row, column, target);
408     }
409
410     if (field.hasInstances()) {
411         return fetchInstances(field, model, row, column, target);
412     }
413
414     const QString fieldId(QTrackerContactQueryBuilder::name(target.detailName(), field.name()));
415     QString columnName(model->headerData(column, Qt::Horizontal).toString());
416
417     Q_ASSERT_X(columnName == fieldId, qPrintable(fieldId),
418                qPrintable("unexpected column title: " + columnName));
419
420     const QModelIndex index(model->index(row, column++));
421     QVariant value(model->data(index));
422
423     if (value.isNull()) {
424         return column;
425     }
426
427     if (not value.convert(field.dataType())) {
428         qWarning() << qPrintable(fieldId)
429                    << ": cannot convert from" << value.typeName()
430                    << "to" << QVariant::typeToName(field.dataType());
431         return column;
432     }
433
434     if ((QVariant::String == value.type() && value.toString().isEmpty()) ||
435         (QVariant::StringList == value.type() && value.toStringList().isEmpty())) {
436         return column;
437     }
438
439     target->setValue(field.name(), value);
440
441     return column;
442 }
443
444 void
445 QTrackerContactFetchRequest2::fetchBaseModel(QContactMap &results)
446 {
447     LiveNodeModel *model(mLiveNodes.first().model());
448     Q_ASSERT(not model->canFetchMore(QModelIndex()));
449
450     for(int row = 0; row < model->rowCount(); ++row) {
451         // find local contact Id
452         const QString contactIri(model->data(model->index(row, 0)).toString());
453         QContactLocalId localId = 0;
454         bool haveValidId = false;
455
456         localId = parseContactIri(contactIri, &haveValidId);
457         // TODO: handle special contacts like self-contact or emergency number
458
459         if (not haveValidId) {
460             qWarning() << "skipping contact with unsupported IRI:" << contactIri;
461             continue;
462         }
463
464         // create new contact
465         QContactId id;
466         id.setLocalId(localId);
467         id.setManagerUri(mEngine->managerUri());
468
469         QContact contact;
470         contact.setId(id);
471
472         // read unique details
473         foreach(const QTrackerContactDetailNode &node, mDetails) {
474             if (not node.detail().isUnique()) {
475                 continue;
476             }
477
478             QContactDetailPointer detail(contactIri, node.detail());
479             int column = node.firstColumn();
480
481             foreach(const QTrackerContactDetailField &field, node.detail().fields()) {
482                 Q_ASSERT_X(not field.hasSubTypes(), qPrintable(field.name()),
483                            "unique details cannot have subtypes");
484
485                 column = fetchField(field, model, row, column, detail);
486             }
487
488             Q_ASSERT_X(column == node.lastColumn(), qPrintable(node.detail().name()),
489                        qPrintable(QString::fromLatin1("actual: %1, expected: %2").
490                                   arg(column).arg(node.lastColumn())));
491
492             if (not detail.isNull()) {
493                 contact.saveDetail(detail.data());
494             }
495         }
496
497         // store the fetched contact
498         results.insert(contactIri, contact);
499     }
500 }
501
502 void
503 QTrackerContactFetchRequest2::fetchDetailModel(QContactMap &results,
504                                                const QTrackerContactDetailNode &node)
505 {
506     // unique details have their properties attached to the base model
507     if (node.detail().isUnique()) {
508         return;
509     }
510
511     QContactMapIter cursor(results.begin());
512     LiveNodeModel *model(mLiveNodes[node.queryOffset()].model());
513     Q_ASSERT(not model->canFetchMore(QModelIndex()));
514     const QString &detailName(node.detail().name());
515
516     for(int row = 0; row < model->rowCount(); ++row) {
517         const QString contactIri(model->data(model->index(row, 0)).toString());
518
519         // find the already constructed contact matching this row
520         while (cursor != results.end() && cursor.key() < contactIri) {
521             ++cursor;
522         }
523
524         if (cursor == results.end() || cursor.key() != contactIri) {
525             qWarning() << detailName << ":" << contactIri << ": skipping unexpected contact";
526             continue;
527         }
528
529         QContactDetailPointer detail(contactIri, node.detail());
530         int lastColumn = 1;
531
532         // read content URI column
533         if (detail.hasSubjectDomain()) {
534             const QModelIndex index(model->index(row, lastColumn++));
535             detail.setSubject(model->data(index).toString());
536         }
537
538         // read all regular columns
539         foreach(const QTrackerContactDetailField &field, node.detail().fields()) {
540             lastColumn = fetchField(field, model, row, lastColumn, detail);
541         }
542
543         // read context column
544         if (node.detail().hasContext()) {
545             const QModelIndex index(model->index(row, lastColumn++));
546
547             QVariant contextBound(model->data(index));
548             bool success = contextBound.convert(QVariant::Bool);
549             Q_ASSERT(success && QVariant::Bool == contextBound.type());
550
551             if (not detail.isNull()) {
552                 if (contextBound.toBool()) {
553                     detail->setContexts(QContactDetail::ContextWork);
554                 } else {
555                     detail->setContexts(QContactDetail::ContextHome);
556                 }
557             }
558         }
559
560         // save updates to the contact detail
561         if (not detail.isNull()) {
562             cursor.value().saveDetail(detail.data());
563         }
564
565         // check that really all columns were read
566         Q_ASSERT_X(lastColumn == model->columnCount(), qPrintable(detailName),
567                    qPrintable(QString::fromLatin1("fetched: %1, model: %2").
568                               arg(lastColumn).arg(model->columnCount())));
569     }
570
571     qWarning() << "TODO: check if column names match";
572 }
573
574 void
575 QTrackerContactFetchRequest2::modelUpdated()
576 {
577     LiveNodeModel *model(qobject_cast<LiveNodeModel *>(sender()));
578
579     Q_ASSERT(0 != model);
580     mPending.remove(model);
581     qDebug() << *model;
582
583     if (mPending.isEmpty()) {
584         QContactMap contacts;
585         fetchBaseModel(contacts);
586
587         foreach(const QTrackerContactDetailNode &detail, mDetails) {
588             fetchDetailModel(contacts, detail);
589         }
590
591         // Update Display Labels
592         foreach (const QContact &contact, contacts.values()) {
593             const QContactDisplayLabel dl(contact.detail(QContactDisplayLabel::DefinitionName));
594             if (dl.label().isEmpty()) {
595                 QContactManager::Error synthError;
596                 mEngine->setContactDisplayLabel(mEngine->synthesizedDisplayLabel(contact, &synthError), contact);
597             }
598         }
599
600         mEngine->updateContactFetchRequest(mRequest, contacts.values(), QContactManager::NoError,
601                                            QContactAbstractRequest::FinishedState);
602
603         Q_ASSERT(0 != mEventLoop);
604         mEventLoop->quit();
605     }
606 }
607
608 ///////////////////////////////////////////////////////////////////////////////////////////////////
609
610 #include "moc_contactfetchrequest2.cpp"
611
612 QTM_END_NAMESPACE;