Initial Qt5 port
[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 #if defined(QT4)
27 #include <QDeclarativeInfo>
28 #elif defined(QT5)
29 #include <QQmlInfo>
30 #endif
31 #include <QDateTime>
32 #include <QDBusConnection>
33 #include <QStringList>
34 #include <QDBusMetaType>
35 #include <QDBusArgument>
36
37 #define DRIVER "QTRACKER_DIRECT"
38 #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)"
39
40 #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)) }"
41
42 #define TRACKER_SERVICE "org.freedesktop.Tracker1"
43 #define TRACKER_RESOURCE_PATH "/org/freedesktop/Tracker1/Resources"
44 #define TRACKER_RESOURCE_INTERFACE "org.freedesktop.Tracker1.Resources"
45 #define TRACKER_RESOURCE_SIGNAL "GraphUpdated"
46 #define PHOTO_CLASS "http://www.tracker-project.org/temp/nmm#Photo"
47 #define VIDEO_CLASS "http://www.tracker-project.org/temp/nmm#Video"
48 #define TRACKER_RESOURCE_SIGNAL_SIGNATURE "sa(iiii)a(iiii)"
49
50 class Quad {
51 public:
52   int graph;
53   int subject;
54   int predicate;
55   int object;
56 };
57
58 Q_DECLARE_METATYPE(Quad);
59 Q_DECLARE_METATYPE(QList<Quad>);
60
61 QDBusArgument& operator<<(QDBusArgument& argument, const Quad& t) {
62   argument.beginStructure();
63   argument << t.graph << t.subject << t.predicate << t.object;
64   argument.endStructure();
65   return argument;
66 }
67
68 const QDBusArgument& operator>>(const QDBusArgument& argument, Quad& t) {
69   argument.beginStructure();
70   argument >> t.graph >> t.subject >> t.predicate >> t.object;
71   argument.endStructure();
72   return argument;
73 }
74
75 PostCaptureModel::PostCaptureModel(QObject *parent) :
76   QAbstractListModel(parent),
77   m_connection(0),
78   m_connected(false) {
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 QHash<int, QByteArray> PostCaptureModel::roleNames() const {
92   QHash<int, QByteArray> roles;
93   roles.insert(Item, "item");
94   return roles;
95 }
96
97 void PostCaptureModel::reload() {
98   delete m_connection; m_connection = 0;
99
100   if (!m_items.isEmpty()) {
101     beginRemoveRows(QModelIndex(), 0, m_items.size() - 1);
102     qDeleteAll(m_items);
103     m_items.clear();
104     endRemoveRows();
105   }
106
107   m_connection = new QSparqlConnection(DRIVER, QSparqlConnectionOptions(), this);
108   if (!m_connection->isValid()) {
109     emit error(tr("Failed to create SPARQL connection"));
110     return;
111   }
112
113   QString equipment = QString("urn:equipment:%1:%2:").arg(m_manufacturer).arg(m_model);
114
115   QSparqlQuery q(QUERY, QSparqlQuery::SelectStatement);
116   q.bindValue("equipment", equipment);
117   exec(q);
118
119   if (!m_connected) {
120     const char *slot = SLOT(graphUpdated(const QString&,
121                                          const QList<Quad>&,
122                                          const QList<Quad>&));
123     m_connected = QDBusConnection::sessionBus().connect(TRACKER_SERVICE, TRACKER_RESOURCE_PATH,
124                                                         TRACKER_RESOURCE_INTERFACE,
125                                                         TRACKER_RESOURCE_SIGNAL,
126                                                         TRACKER_RESOURCE_SIGNAL_SIGNATURE,
127                                                         this, slot);
128   }
129
130   if (!m_connected) {
131     qmlInfo(this) << "Failed to connect to tracker " << TRACKER_RESOURCE_SIGNAL;
132   }
133 }
134
135 void PostCaptureModel::exec(QSparqlQuery& query) {
136   if (!m_connection) {
137     qWarning() << "No connection to query";
138     return;
139   }
140
141   QSparqlResult *result = m_connection->exec(query);
142
143   if (result->hasError()) {
144     QSparqlError error = result->lastError();
145     qmlInfo(this) << "Error executing SPARQL query" << error.message();
146
147     delete result;
148
149     emit PostCaptureModel::error(tr("Failed to query tracker"));
150   }
151
152   if (result) {
153     QObject::connect(result, SIGNAL(dataReady(int)), this, SLOT(dataReady(int)));
154     QObject::connect(result, SIGNAL(finished()), result, SLOT(deleteLater()));
155   }
156 }
157
158 int PostCaptureModel::rowCount(const QModelIndex& parent) const {
159   if (parent.isValid()) {
160     return 0;
161   }
162
163   return m_items.size();
164 }
165
166 QVariant PostCaptureModel::data(const QModelIndex& index, int role) const {
167   if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size()) {
168     return QVariant();
169   }
170
171   if (role == Item) {
172     return QVariant::fromValue(dynamic_cast<QObject *>(m_items[index.row()]));
173   }
174
175   return QVariant();
176 }
177
178 QString PostCaptureModel::manufacturer() const {
179   return m_manufacturer;
180 }
181
182 void PostCaptureModel::setManufacturer(const QString& manufacturer) {
183   if (m_manufacturer != manufacturer) {
184     m_manufacturer = manufacturer;
185     emit manufacturerChanged();
186   }
187 }
188
189 QString PostCaptureModel::model() const {
190   return m_model;
191 }
192
193 void PostCaptureModel::setModel(const QString& model) {
194   if (m_model != model) {
195     m_model = model;
196     emit modelChanged();
197   }
198 }
199
200 void PostCaptureModel::dataReady(int totalCount) {
201   Q_UNUSED(totalCount);
202
203   QSparqlResult *result = dynamic_cast<QSparqlResult *>(sender());
204   if (!result) {
205     return;
206   }
207
208   while (result->next()) {
209     addRow(new PostCaptureModelItem(result->current(), this));
210   }
211
212   result->previous();
213 }
214
215 void PostCaptureModel::addRow(PostCaptureModelItem *item) {
216   if (m_hash.contains(item->trackerId())) {
217     m_hash[item->trackerId()]->update(item);
218     delete item;
219     return;
220   }
221
222   beginInsertRows(QModelIndex(), m_items.size(), m_items.size());
223   m_items << item;
224   m_hash.insert(item->trackerId(), item);
225
226   endInsertRows();
227 }
228
229 void PostCaptureModel::remove(QObject *item) {
230   PostCaptureModelItem *i = dynamic_cast<PostCaptureModelItem *>(item);
231   if (!i) {
232     qmlInfo(this) << "Invalid item to remove";
233     return;
234   }
235
236   int index = m_items.indexOf(i);
237   if (index == -1) {
238     qmlInfo(this) << "Item" << i->trackerId() << "not found in model";
239     return;
240   }
241
242   beginRemoveRows(QModelIndex(), index, index);
243   m_items.takeAt(index);
244   m_hash.remove(i->trackerId());
245   endRemoveRows();
246
247   i->deleteLater();
248 }
249
250 void PostCaptureModel::graphUpdated(const QString& className, const QList<Quad>& deleted,
251                                     const QList<Quad>& inserted) {
252
253   // We will just assume tracker:available has changed and that's it.
254   // We are not really interested in the rest of properties.
255
256   if (!(className == QLatin1String(PHOTO_CLASS) || className == QLatin1String(VIDEO_CLASS))) {
257     return;
258   }
259
260   QList<int> items;
261
262   for (int x = 0; x < deleted.size(); x++) {
263     Quad q = deleted[x];
264     if (m_hash.contains(q.subject) && items.indexOf(q.subject) == -1) {
265       items << q.subject;
266     }
267   }
268
269   for (int x = 0; x < inserted.size(); x++) {
270     Quad q = inserted[x];
271     if (m_hash.contains(q.subject) && items.indexOf(q.subject) == -1) {
272       items << q.subject;
273     }
274   }
275
276   for (int x = 0; x < items.size(); x++) {
277     QString query = QString(UPDATE_QUERY).arg(items[x]);
278     QSparqlQuery q(query, QSparqlQuery::SelectStatement);
279
280     exec(q);
281   }
282 }
283
284 PostCaptureModelItem::PostCaptureModelItem(const QSparqlResultRow& row, QObject *parent) :
285   QObject(parent) {
286
287   for (int x = 0; x < row.count(); x++) {
288     QSparqlBinding b = row.binding(x);
289     m_data.insert(b.name(), b.value());
290   }
291 }
292
293 QString PostCaptureModelItem::type() const {
294   return value("type").toString();
295 }
296
297 QUrl PostCaptureModelItem::url() const {
298   return value("url").toUrl();
299 }
300
301 QString PostCaptureModelItem::created() const {
302   return formatDateTime(value("created").toDateTime());
303 }
304
305 QString PostCaptureModelItem::title() const {
306   return value("title").toString();
307 }
308
309 QString PostCaptureModelItem::fileName() const {
310   return value("filename").toString();
311 }
312
313 QString PostCaptureModelItem::mimeType() const {
314   return value("mimetype").toString();
315 }
316
317 bool PostCaptureModelItem::available() const {
318   return value("available", false).toBool();
319 }
320
321 QString PostCaptureModelItem::lastModified() const {
322   return formatDateTime(value("lastmod").toDateTime());
323 }
324
325 unsigned PostCaptureModelItem::trackerId() const {
326   return value("trackerid").toUInt();
327 }
328
329 bool PostCaptureModelItem::favorite() const {
330   return value("favorite", false).toBool();
331 }
332
333 void PostCaptureModelItem::setFavorite(bool add) {
334   if (favorite() != add) {
335     m_data["favorite"] = add;
336
337     emit favoriteChanged();
338   }
339 }
340
341 QString PostCaptureModelItem::formatDateTime(const QDateTime& dt) const {
342   return dt.toString();
343 }
344
345 void PostCaptureModelItem::update(PostCaptureModelItem *other) {
346   // We will only update available, favorite and title:
347 #if 0
348   qDebug() << "i" << trackerId() << other->trackerId()  << "\n"
349            << "a" << available() << other->available() << "\n"
350            << "t" << title() << other->title() << "\n"
351            << "f" << favorite() << other->favorite();
352 #endif
353
354   if (available() != other->available()) {
355     m_data["available"] = other->available();
356     emit availableChanged();
357   }
358
359   if (title() != other->title()) {
360     m_data["title"] = other->title();
361     emit titleChanged();
362   }
363
364   if (favorite() != other->favorite()) {
365     m_data["favorite"] = other->favorite();
366     emit favoriteChanged();
367   }
368 }
369
370 QVariant PostCaptureModelItem::value(const QString& id, const QVariant& def) const {
371   return m_data.contains(id) ? m_data[id] : def;
372 }