Changes: Use QString instead of QUrl for Iris
[qtcontacts-tracker:qtcontacts-tracker.git] / src / engine / trackerchangelistener.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt Mobility Components.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "trackerchangelistener.h"
43
44 #include <dao/contactdetailschema.h>
45 #include <dao/logger.h>
46 #include <dao/ontologies/nco.h>
47 #include <dao/sparqlresolver.h>
48 #include <engine/engine.h>
49
50 ////////////////////////////////////////////////////////////////////////////////////////////////////
51
52 CUBI_USE_NAMESPACE
53 CUBI_USE_NAMESPACE_RESOURCES
54
55 ////////////////////////////////////////////////////////////////////////////////////////////////////
56
57 QctTrackerChangeListener::QctTrackerChangeListener(QContactTrackerEngine *engine, QObject *parent)
58     : QObject(parent)
59     , m_debugSignals(engine->hasDebugFlag(QContactTrackerEngine::ShowSignals))
60 {
61     // setup m_signalCoalescingTimer
62     m_signalCoalescingTimer.setInterval(engine->coalescingDelay());
63     m_signalCoalescingTimer.setSingleShot(true);
64     m_signalCoalescingTimer.stop();
65
66     connect(&m_signalCoalescingTimer, SIGNAL(timeout()), SLOT(emitQueuedNotifications()));
67
68     // setup change notification listeners
69     QSet<QString> contactClassIris;
70
71     foreach(const QTrackerContactDetailSchema &schema, engine->schemas()) {
72         // Must monitor individual contact classes instead of nco:Contact
73         // to avoid bogus notifications for:
74         //
75         //  - QContactOrganization details, implemented via nco:OrganizationContact
76         //  - incoming calls which cause call-ui to create temporary(?) nco:Contact instances
77         //
78         foreach(const QString &iri, schema.contactClassIris()) {
79             if (iri != nco::Contact::iri()) {
80                 contactClassIris += iri;
81             }
82         }
83     }
84
85     foreach(const QString &iri, contactClassIris) {
86         connect(new TrackerChangeNotifier(iri, this),
87                 SIGNAL(changed(QList<TrackerChangeNotifier::Quad>,
88                                QList<TrackerChangeNotifier::Quad>)),
89                 SLOT(onGraphChanged(QList<TrackerChangeNotifier::Quad>,
90                                     QList<TrackerChangeNotifier::Quad>)));
91     }
92
93     // store some frequently needed resource ids
94     QStringList resourceIris = QStringList() << nco::belongsToGroup::iri()
95                                              << nco::contactLocalUID::iri()
96                                              << rdf::type::iri()
97                                              << contactClassIris.toList();
98
99     QScopedPointer<QctTrackerIdResolver> resolver(new QctTrackerIdResolver(resourceIris));
100     connect(resolver.data(), SIGNAL(finished()), SLOT(onTrackerIdsResolved()));
101
102     if (not resolver->lookup()) {
103         qctWarn("Cannot resolve commonly resource ids. Change notifications won't work.");
104     } else {
105         resolver.take()->setParent(this);
106     }
107 }
108
109 QctTrackerChangeListener::~QctTrackerChangeListener()
110 {
111 }
112
113 void
114 QctTrackerChangeListener::onTrackerIdsResolved()
115 {
116     QctTrackerIdResolver *const resolver = qobject_cast<QctTrackerIdResolver *>(sender());
117
118     if (0 == resolver) {
119         qctWarn("Invalid sender");
120     }
121
122     QList<uint> trackerIds = resolver->trackerIds();
123     m_trackerIds.belongsToGroup = trackerIds.takeFirst();
124     m_trackerIds.contactLocalUID = trackerIds.takeFirst();
125     m_trackerIds.rdfType = trackerIds.takeFirst();
126     m_trackerIds.contactClasses = trackerIds;
127
128     resolver->deleteLater();
129     emitQueuedNotifications();
130 }
131
132 void
133 QctTrackerChangeListener::processNotifications(QList<TrackerChangeNotifier::Quad> &notifications,
134                                                QSet<QContactLocalId> &additionsOrRemovals,
135                                                QSet<QContactLocalId> &relationshipChanges,
136                                                QSet<QContactLocalId> &propertyChanges)
137 {
138     foreach(const TrackerChangeNotifier::Quad &quad, notifications) {
139         // Changes to rdf:type are the most reliable indication of resource creation and removal.
140         if (m_trackerIds.rdfType == uint(quad.predicate)) {
141             if (m_trackerIds.contactClasses.contains(quad.object)) {
142                 additionsOrRemovals += quad.subject;
143             }
144             continue;
145         }
146
147         // Changes to nco:belongsToGroup indicate group membership changes.
148         if (m_trackerIds.belongsToGroup == uint(quad.predicate)) {
149             relationshipChanges += quad.subject;
150             relationshipChanges += quad.object;
151             continue;
152         }
153
154         // All other property changes indicate a contact change,
155         // expect for changes to nco:contactLocalUID which happen
156         // when that property is updated for backward compatiblity.
157         if (m_trackerIds.contactLocalUID != uint(quad.predicate)) {
158             propertyChanges += quad.subject;
159         }
160     }
161
162     // Hopefully not bogus optimization:
163     // Avoid change notifications wich overlap with addition or removal notifications.
164     propertyChanges -= additionsOrRemovals;
165
166     // Clear processed notifcation queue.
167     notifications.clear();
168 }
169
170 void
171 QctTrackerChangeListener::onGraphChanged(const QList<TrackerChangeNotifier::Quad>& deletes,
172                                          const QList<TrackerChangeNotifier::Quad>& inserts)
173 {
174     if (m_debugSignals) {
175         TrackerChangeNotifier *const notifier = static_cast<TrackerChangeNotifier *>(sender());
176
177         qDebug() << notifier->watchedClass() << "- deletes:" << deletes;
178         qDebug() << notifier->watchedClass() << "- inserts:" << inserts;
179     }
180
181     m_deleteNotifications += deletes;
182     m_insertNotifications += inserts;
183
184     // minimally delay signal emission to give a chance for signals getting coalesced
185     if (not m_trackerIds.contactClasses.isEmpty() && not m_signalCoalescingTimer.isActive()) {
186         m_signalCoalescingTimer.start();
187     }
188 }
189
190 void
191 QctTrackerChangeListener::emitQueuedNotifications()
192 {
193     QSet<QContactLocalId> contactsAddedIds, contactsRemovedIds, contactsChangedIds;
194     QSet<QContactLocalId> relationshipsAddedIds, relationshipsRemovedIds;
195
196     // process notification queues...
197     processNotifications(m_deleteNotifications, contactsRemovedIds,
198                          relationshipsRemovedIds, contactsChangedIds);
199     processNotifications(m_insertNotifications, contactsAddedIds,
200                          relationshipsAddedIds, contactsChangedIds);
201
202     // ...report identified changes when requested...
203     if (m_debugSignals) {
204         qDebug() << "added contacts:" << contactsAddedIds.count() << contactsAddedIds;
205         qDebug() << "changed contacts:" << contactsChangedIds.count() << contactsChangedIds;
206         qDebug() << "removed contacts:" << contactsRemovedIds.count() << contactsRemovedIds;
207         qDebug() << "added relationships:" << relationshipsAddedIds.count() << relationshipsAddedIds;
208         qDebug() << "removed relationships:" << relationshipsRemovedIds.count() << relationshipsRemovedIds;
209     }
210
211     // ...and finally emit the signals.
212     if (not contactsAddedIds.isEmpty()) {
213         emit contactsAdded(contactsAddedIds.toList());
214     }
215
216     if (not contactsChangedIds.isEmpty()) {
217         emit contactsChanged(contactsChangedIds.toList());
218     }
219
220     if (not contactsRemovedIds.isEmpty()) {
221         emit contactsRemoved(contactsRemovedIds.toList());
222     }
223
224     if (not relationshipsAddedIds.isEmpty()) {
225         emit relationshipsAdded(relationshipsAddedIds.toList());
226     }
227
228     if (not relationshipsRemovedIds.isEmpty()) {
229         emit relationshipsRemoved(relationshipsRemovedIds.toList());
230     }
231 }