Don't connect to GraphUpdated more than once.
[harmattan/cameraplus] / src / postcapturemodel.cpp
1 /*!
2  * This file is part of CameraPlus.
3  *
4  * Copyright (C) 2012-2013 Mohammed Sameer <msameer@foolab.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21 #include "postcapturemodel.h"
22 #include <QSparqlConnection>
23 #include <QSparqlQuery>
24 #include <QSparqlResult>
25 #include <QSparqlError>
26 #include <QDeclarativeInfo>
27 #include <QDateTime>
28 #include <QDBusConnection>
29 #include <QStringList>
30 #include <QDBusMetaType>
31 #include <QDBusArgument>
32
33 #define DRIVER "QTRACKER_DIRECT"
34 #define QUERY "SELECT rdf:type(?urn) AS ?type nie:url(?urn) AS ?url nie:contentCreated(?urn) AS ?created nie:title(?urn) AS ?title nfo:fileName(?urn) AS ?filename nie:mimeType(?urn) AS ?mimetype tracker:available(?urn) AS ?available nfo:fileLastModified(?urn) as ?lastmod tracker:id(?urn) AS ?trackerid (EXISTS { ?urn nao:hasTag nao:predefined-tag-favorite }) AS ?favorite WHERE { ?urn nfo:equipment ?:equipment . {?urn a nfo:Video} UNION {?urn a nfo:Image}} ORDER BY DESC(?created)"
35
36 #define UPDATE_QUERY "SELECT rdf:type(?urn) AS ?type nie:url(?urn) AS ?url nie:contentCreated(?urn) AS ?created nie:title(?urn) AS ?title nfo:fileName(?urn) AS ?filename nie:mimeType(?urn) AS ?mimetype tracker:available(?urn) AS ?available nfo:fileLastModified(?urn) as ?lastmod tracker:id(?urn) AS ?trackerid (EXISTS { ?urn nao:hasTag nao:predefined-tag-favorite }) AS ?favorite WHERE {?urn a nfo:Visual . FILTER (tracker:id(?urn) IN (%1)) }"
37
38 #define TRACKER_SERVICE "org.freedesktop.Tracker1"
39 #define TRACKER_RESOURCE_PATH "/org/freedesktop/Tracker1/Resources"
40 #define TRACKER_RESOURCE_INTERFACE "org.freedesktop.Tracker1.Resources"
41 #define TRACKER_RESOURCE_SIGNAL "GraphUpdated"
42 #define PHOTO_CLASS "http://www.tracker-project.org/temp/nmm#Photo"
43 #define VIDEO_CLASS "http://www.tracker-project.org/temp/nmm#Video"
44 #define TRACKER_RESOURCE_SIGNAL_SIGNATURE "sa(iiii)a(iiii)"
45
46 class Quad {
47 public:
48   int graph;
49   int subject;
50   int predicate;
51   int object;
52 };
53
54 Q_DECLARE_METATYPE(Quad);
55 Q_DECLARE_METATYPE(QList<Quad>);
56
57 QDBusArgument& operator<<(QDBusArgument& argument, const Quad& t) {
58   argument.beginStructure();
59   argument << t.graph << t.subject << t.predicate << t.object;
60   argument.endStructure();
61   return argument;
62 }
63
64 const QDBusArgument& operator>>(const QDBusArgument& argument, Quad& t) {
65   argument.beginStructure();
66   argument >> t.graph >> t.subject >> t.predicate >> t.object;
67   argument.endStructure();
68   return argument;
69 }
70
71 PostCaptureModel::PostCaptureModel(QObject *parent) :
72   QAbstractListModel(parent),
73   m_connection(0),
74   m_connected(false) {
75
76   QHash<int, QByteArray> roles;
77   roles.insert(Item, "item");
78   setRoleNames(roles);
79
80   qDBusRegisterMetaType<Quad>();
81   qDBusRegisterMetaType<QList<Quad> >();
82 }
83
84 PostCaptureModel::~PostCaptureModel() {
85   qDeleteAll(m_items);
86   m_items.clear();
87
88   delete m_connection; m_connection = 0;
89 }
90
91 void PostCaptureModel::classBegin() {
92
93 }
94
95 void PostCaptureModel::componentComplete() {
96
97 }
98
99 void PostCaptureModel::reload() {
100   delete m_connection; m_connection = 0;
101
102   if (!m_items.isEmpty()) {
103     beginRemoveRows(QModelIndex(), 0, m_items.size() - 1);
104     qDeleteAll(m_items);
105     m_items.clear();
106     endRemoveRows();
107   }
108
109   m_connection = new QSparqlConnection(DRIVER, QSparqlConnectionOptions(), this);
110   if (!m_connection->isValid()) {
111     emit error(tr("Failed to create SPARQL connection"));
112     return;
113   }
114
115   QString equipment = QString("urn:equipment:%1:%2:").arg(m_manufacturer).arg(m_model);
116
117   QSparqlQuery q(QUERY, QSparqlQuery::SelectStatement);
118   q.bindValue("equipment", equipment);
119   exec(q);
120
121   if (!m_connected) {
122     const char *slot = SLOT(graphUpdated(const QString&,
123                                          const QList<Quad>&,
124                                          const QList<Quad>&));
125     m_connected = QDBusConnection::sessionBus().connect(TRACKER_SERVICE, TRACKER_RESOURCE_PATH,
126                                                         TRACKER_RESOURCE_INTERFACE,
127                                                         TRACKER_RESOURCE_SIGNAL,
128                                                         TRACKER_RESOURCE_SIGNAL_SIGNATURE,
129                                                         this, slot);
130   }
131
132   if (!m_connected) {
133     qmlInfo(this) << "Failed to connect to tracker " << TRACKER_RESOURCE_SIGNAL;
134   }
135 }
136
137 void PostCaptureModel::exec(QSparqlQuery& query) {
138   if (!m_connection) {
139     qWarning() << "No connection to query";
140     return;
141   }
142
143   QSparqlResult *result = m_connection->exec(query);
144
145   if (result->hasError()) {
146     QSparqlError error = result->lastError();
147     qmlInfo(this) << "Error executing SPARQL query" << error.message();
148
149     delete result;
150
151     emit PostCaptureModel::error(tr("Failed to query tracker"));
152   }
153
154   if (result) {
155     QObject::connect(result, SIGNAL(dataReady(int)), this, SLOT(dataReady(int)));
156     QObject::connect(result, SIGNAL(finished()), result, SLOT(deleteLater()));
157   }
158 }
159
160 int PostCaptureModel::rowCount(const QModelIndex& parent) const {
161   if (parent.isValid()) {
162     return 0;
163   }
164
165   return m_items.size();
166 }
167
168 QVariant PostCaptureModel::data(const QModelIndex& index, int role) const {
169   if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size()) {
170     return QVariant();
171   }
172
173   if (role == Item) {
174     return QVariant::fromValue(dynamic_cast<QObject *>(m_items[index.row()]));
175   }
176
177   return QVariant();
178 }
179
180 QString PostCaptureModel::manufacturer() const {
181   return m_manufacturer;
182 }
183
184 void PostCaptureModel::setManufacturer(const QString& manufacturer) {
185   if (m_manufacturer != manufacturer) {
186     m_manufacturer = manufacturer;
187     emit manufacturerChanged();
188   }
189 }
190
191 QString PostCaptureModel::model() const {
192   return m_model;
193 }
194
195 void PostCaptureModel::setModel(const QString& model) {
196   if (m_model != model) {
197     m_model = model;
198     emit modelChanged();
199   }
200 }
201
202 void PostCaptureModel::dataReady(int totalCount) {
203   Q_UNUSED(totalCount);
204
205   QSparqlResult *result = dynamic_cast<QSparqlResult *>(sender());
206   if (!result) {
207     return;
208   }
209
210   while (result->next()) {
211     addRow(new PostCaptureModelItem(result->current(), this));
212   }
213
214   result->previous();
215 }
216
217 void PostCaptureModel::addRow(PostCaptureModelItem *item) {
218   if (m_hash.contains(item->trackerId())) {
219     m_hash[item->trackerId()]->update(item);
220     delete item;
221     return;
222   }
223
224   beginInsertRows(QModelIndex(), m_items.size(), m_items.size());
225   m_items << item;
226   m_hash.insert(item->trackerId(), item);
227
228   endInsertRows();
229 }
230
231 void PostCaptureModel::remove(QObject *item) {
232   PostCaptureModelItem *i = dynamic_cast<PostCaptureModelItem *>(item);
233   if (!i) {
234     qmlInfo(this) << "Invalid item to remove";
235     return;
236   }
237
238   int index = m_items.indexOf(i);
239   if (index == -1) {
240     qmlInfo(this) << "Item" << i->trackerId() << "not found in model";
241     return;
242   }
243
244   beginRemoveRows(QModelIndex(), index, index);
245   m_items.takeAt(index);
246   m_hash.remove(i->trackerId());
247   endRemoveRows();
248
249   i->deleteLater();
250 }
251
252 void PostCaptureModel::graphUpdated(const QString& className, const QList<Quad>& deleted,
253                                     const QList<Quad>& inserted) {
254
255   // We will just assume tracker:available has changed and that's it.
256   // We are not really interested in the rest of properties.
257
258   if (!(className == QLatin1String(PHOTO_CLASS) || className == QLatin1String(VIDEO_CLASS))) {
259     return;
260   }
261
262   QList<int> items;
263
264   for (int x = 0; x < deleted.size(); x++) {
265     Quad q = deleted[x];
266     if (m_hash.contains(q.subject) && items.indexOf(q.subject) == -1) {
267       items << q.subject;
268     }
269   }
270
271   for (int x = 0; x < inserted.size(); x++) {
272     Quad q = inserted[x];
273     if (m_hash.contains(q.subject) && items.indexOf(q.subject) == -1) {
274       items << q.subject;
275     }
276   }
277
278   for (int x = 0; x < items.size(); x++) {
279     QString query = QString(UPDATE_QUERY).arg(items[x]);
280     QSparqlQuery q(query, QSparqlQuery::SelectStatement);
281
282     exec(q);
283   }
284 }
285
286 PostCaptureModelItem::PostCaptureModelItem(const QSparqlResultRow& row, QObject *parent) :
287   QObject(parent) {
288
289   for (int x = 0; x < row.count(); x++) {
290     QSparqlBinding b = row.binding(x);
291     m_data.insert(b.name(), b.value());
292   }
293 }
294
295 QString PostCaptureModelItem::type() const {
296   return value("type").toString();
297 }
298
299 QUrl PostCaptureModelItem::url() const {
300   return value("url").toUrl();
301 }
302
303 QString PostCaptureModelItem::created() const {
304   return formatDateTime(value("created").toDateTime());
305 }
306
307 QString PostCaptureModelItem::title() const {
308   return value("title").toString();
309 }
310
311 QString PostCaptureModelItem::fileName() const {
312   return value("filename").toString();
313 }
314
315 QString PostCaptureModelItem::mimeType() const {
316   return value("mimetype").toString();
317 }
318
319 bool PostCaptureModelItem::available() const {
320   return value("available", false).toBool();
321 }
322
323 QString PostCaptureModelItem::lastModified() const {
324   return formatDateTime(value("lastmod").toDateTime());
325 }
326
327 unsigned PostCaptureModelItem::trackerId() const {
328   return value("trackerid").toUInt();
329 }
330
331 bool PostCaptureModelItem::favorite() const {
332   return value("favorite", false).toBool();
333 }
334
335 void PostCaptureModelItem::setFavorite(bool add) {
336   if (favorite() != add) {
337     m_data["favorite"] = add;
338
339     emit favoriteChanged();
340   }
341 }
342
343 QString PostCaptureModelItem::formatDateTime(const QDateTime& dt) const {
344   return dt.toString();
345 }
346
347 void PostCaptureModelItem::update(PostCaptureModelItem *other) {
348   // We will only update available, favorite and title:
349 #if 0
350   qDebug() << "i" << trackerId() << other->trackerId()  << "\n"
351            << "a" << available() << other->available() << "\n"
352            << "t" << title() << other->title() << "\n"
353            << "f" << favorite() << other->favorite();
354 #endif
355
356   if (available() != other->available()) {
357     m_data["available"] = other->available();
358     emit availableChanged();
359   }
360
361   if (title() != other->title()) {
362     m_data["title"] = other->title();
363     emit titleChanged();
364   }
365
366   if (favorite() != other->favorite()) {
367     m_data["favorite"] = other->favorite();
368     emit favoriteChanged();
369   }
370 }
371
372 QVariant PostCaptureModelItem::value(const QString& id, const QVariant& def) const {
373   return m_data.contains(id) ? m_data[id] : def;
374 }