Fixes: NB#270945 - Empty share UI is launched when trying to share attached picture
[meego-sharing-framework:share-ui.git] / share-ui-common / src / fileitem.cpp
1  
2 /*
3  * share-ui -- Handset UX Share user interface
4  * Copyright (c) 2010-2011 Nokia Corporation and/or its subsidiary(-ies).
5  * Contact: Jukka Tiihonen <jukka.t.tiihonen@nokia.com>
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms and conditions of the GNU Lesser General Public License,
9  * version 2.1, as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope it will be useful, but WITHOUT ANY
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include "ShareUI/FileItem"
22 #include "fileitem_p.h"
23 #include <QtSparql>
24 #include <QFileInfo>
25 #include <QDebug>
26 #include <contentinfo.h>
27
28 using namespace ShareUI;
29
30 #define STREAM_PREFIX "FileItem:"
31 #define DBG_STREAM qDebug() << STREAM_PREFIX
32 #define WARN_STREAM qWarning() << STREAM_PREFIX
33 #define CRIT_STREAM qCritical() << STREAM_PREFIX
34
35
36 FileItem::FileItem (const QString & constructInfo, QObject * parent) :
37     Item (constructInfo, parent), d_ptr (new FileItemPrivate()) {
38
39     connect (d_ptr, SIGNAL(ready()), this, SIGNAL(ready()));
40     connect (d_ptr, SIGNAL (thumbnail(QPixmap)), this,
41         SIGNAL (thumbnail(QPixmap)));
42 }
43
44 FileItem::~FileItem () {
45     delete d_ptr;
46 }
47
48 SharedItem FileItem::create (const QString & constructInfo) {
49
50     QStringList constructInfoList;
51     constructInfoList << constructInfo;
52
53     QList<SharedItem> list = createList (constructInfoList);
54     SharedItem item;
55     // Ideally, list should never have size more than 1
56     if (list.size () >= 1) {
57         item = list [0];
58     }
59
60     return item;
61 }
62
63 QList<QSharedPointer<Item> > FileItem::createList (
64     const QStringList & constructInfoList) {
65
66     QList<QSharedPointer<Item> > list;
67
68     static QSparqlConnection connection("QTRACKER");
69     if (!connection.isValid()) {
70         CRIT_STREAM << "Tracker driver for QtSparql not found";
71         return list;
72     }
73
74     DBG_STREAM << __FUNCTION__ << "start";
75
76     QStringList fileUrlList;
77     QString fileUrls;
78     QList<QUrl> trackerUriList;
79     QString trackerUris;
80     bool hasTrackerUriAlternative = false;
81     bool hasFileUrlAlternative = false;
82     
83     int alternativeSize = 0;
84     // For each item in constructInfoList, check if it is a tracker IRI or a
85     // fileURI
86     for (int i = 0; i < constructInfoList.count(); ++i) {
87         
88         bool isTrackerIri = true;
89     
90         QUrl fUrl (constructInfoList [i]);
91         
92         if (constructInfoList [i].startsWith ("/") == true) {
93             // Input wasn't url, use fromLocalFile to fix this
94             fUrl = QUrl::fromLocalFile (constructInfoList [i]);
95             isTrackerIri = false;
96         
97         } else if (constructInfoList [i].startsWith ("file://") == true) {
98             fUrl = FileItemPrivate::unencodedUrl(fUrl);
99             isTrackerIri = false;
100             
101         } else if (constructInfoList [i].startsWith ("urn:") == false) {
102             qDebug() << constructInfoList[i]
103                 << "is not tracker file address or file path/uri";
104             continue;
105         }
106     
107         if (isTrackerIri == true) {
108             trackerUriList.append (fUrl);
109             trackerUris.append (QString("'%1',").arg(fUrl.toString()));
110             if (hasTrackerUriAlternative == false) alternativeSize++;
111             hasTrackerUriAlternative = true;
112         } else {
113             //Make sure URI has proper encoding
114             // QUrl::toEncoded does not work since it does not encode the ';'
115             // character (bug 226163). Instead use toPercentEncoding to
116             // converting everything possible, except for the characters given
117             // in the except list to the corresponding percent encoding
118             QByteArray encodedUrl = 
119                 QUrl::toPercentEncoding (fUrl.toString (), ":/()=&@'!,$+");
120             if (encodedUrl.indexOf ('\'') >= 0) {
121                 encodedUrl.insert (encodedUrl.indexOf ('\''), "\\");
122                 qDebug() << "Escaped the \' character";
123             }
124             fileUrlList.append (encodedUrl);
125             DBG_STREAM << "Searching tracker for file:" << encodedUrl;
126             fileUrls.append (QString("'%1',").arg(QString(encodedUrl)));
127             if (hasFileUrlAlternative == false) alternativeSize++;
128             hasFileUrlAlternative = true;
129         }
130     }
131     
132     // Nothing to query
133     if (trackerUriList.isEmpty() == true && fileUrlList.isEmpty() == true) {
134         return list;
135     }
136
137     // Remove the last ',' from the strings
138     trackerUris.chop(1);
139     fileUrls.chop(1);
140
141     QString queryStr = QString(
142         "SELECT ?f tracker:id(?f) ?url ?mime ?size ?title ?desc ?created ?modified ?duration "
143         "WHERE {?f nie:url ?url ; nie:mimeType ?mime . "
144         "OPTIONAL { ?f nie:byteSize ?size } "
145         "OPTIONAL { ?f nie:title ?title } "
146         "OPTIONAL { ?f nie:description ?desc } "
147         "OPTIONAL { ?f nie:contentCreated ?created } "
148         "OPTIONAL { ?f nfo:fileLastModified ?modified } "
149         "OPTIONAL { ?f nfo:duration ?duration } "
150         "FILTER (str(?f) in (%1) || ?url in (%2)) }")
151         .arg(trackerUris)
152         .arg(fileUrls);
153
154     QSparqlQuery query(queryStr);
155     QSparqlResult *result = connection.exec(query);
156     result->waitForFinished();
157     int rowCount = 0;
158     if (result->hasError()) {
159         WARN_STREAM << "Sparql query failed:" << result->lastError().message();
160     }
161     else {
162         DBG_STREAM << "Sparql query ok";
163         rowCount = result->size();
164     }
165
166     QStringList input (constructInfoList);
167     DBG_STREAM << "Tracker reply has" << rowCount << "row(s)";
168     
169     for (int row = 0; row < rowCount; ++row) {
170         result->next();
171         QString tIri = result->binding(0).value().toString ();
172         int trackerId = result->binding(1).value().toInt ();
173         QString fUri = result->binding(2).value().toString ();
174         QString mime = result->binding(3).value().toString ();
175         
176         if (fUri.isEmpty() || mime.isEmpty()) {
177             DBG_STREAM << "Ignoring tracker item" << tIri << fUri << mime;
178             continue;
179         }     
180
181         QString fileSizeStr = result->binding(4).value().toString ();
182         quint64 fileSize = 0;
183         if (fileSizeStr.isEmpty () == false) {
184             fileSize = fileSizeStr.toInt ();
185         }
186     
187         QString fileTitle = result->binding(5).value().toString ();
188         QString fileDesc = result->binding(6).value().toString ();
189         QDateTime created = result->binding(7).value().toDateTime();
190         QDateTime modified = result->binding(8).value().toDateTime();
191         int duration = result->binding(9).value().toInt();
192
193         QString constructInfo;
194         QUrl unencodedFilePathUri = FileItemPrivate::unencodedUrl (fUri);
195         
196         if (input.removeAll (tIri) > 0) {
197             constructInfo = tIri;
198         } else if (input.removeAll (fUri) > 0) {
199             constructInfo = fUri;
200         } else {
201             QString filePath = unencodedFilePathUri.toString (
202                 QUrl::RemoveScheme);
203             
204             // This has to be done manually
205             if (filePath.startsWith ("///") == true) {
206                 filePath = filePath.mid (2);
207             }    
208                 
209             if (input.removeAll (filePath) > 0) {
210                 constructInfo = filePath;
211             } else if (input.removeAll (unencodedFilePathUri.toString()) > 0) {
212                 constructInfo = unencodedFilePathUri.toString();
213             } else {
214                 CRIT_STREAM << "Can't find input value for file" <<
215                     unencodedFilePathUri.toString();
216             }
217         }
218         
219         if (constructInfo.isEmpty () == false) {
220             FileItem * fileItem = new FileItem (constructInfo);
221
222             fileItem->d_ptr->m_trackerIri  = QUrl (tIri);
223             fileItem->d_ptr->m_trackerId = trackerId;
224             fileItem->d_ptr->m_filepathUri = unencodedFilePathUri;
225             fileItem->d_ptr->m_mime = mime;
226             fileItem->d_ptr->m_bytes = fileSize;
227             fileItem->d_ptr->m_title = fileTitle;
228             fileItem->d_ptr->m_desc = fileDesc;
229             fileItem->d_ptr->m_duration = duration;
230             fileItem->d_ptr->m_contentCreated = created;
231             fileItem->d_ptr->m_lastModified = modified;
232             fileItem->d_ptr->m_encodedFileUrl = QUrl::fromEncoded(fUri.toUtf8());
233
234             fileItem->d_ptr->m_sparqlConnection = &connection;
235
236             QSharedPointer<Item> item (fileItem);
237             list.append (item);
238         }
239     }
240
241     delete result;
242     result = 0;
243     
244     DBG_STREAM << input.count() << "item(s) not accepted" << input;
245     DBG_STREAM << __FUNCTION__ << "end";
246
247     return list;
248 }
249
250 QString FileItem::mimeType () {
251     return d_ptr->m_mime;
252 }
253
254 QUrl FileItem::trackerIri () const {
255     return d_ptr->m_trackerIri;
256 }
257
258 QString FileItem::fileTitle () const {
259     return d_ptr->m_title;
260 }
261
262 QString FileItem::fileDescription () const {
263     return d_ptr->m_desc;
264 }
265
266 QString FileItem::fileUri () const {
267     return d_ptr->m_filepathUri.toEncoded();
268 }
269
270 QUrl FileItem::URI() const {
271    return d_ptr->m_encodedFileUrl;
272 }
273
274 QString FileItem::filePath () const {
275    return d_ptr->m_encodedFileUrl.toLocalFile ();
276 }
277
278 int FileItem::duration () const {
279     return d_ptr->m_duration;
280 }
281
282 QDateTime FileItem::contentCreated () const {
283     return d_ptr->m_contentCreated;
284 }
285
286 QDateTime FileItem::lastModified () const {
287     return d_ptr->m_lastModified;
288 }
289
290 quint64 FileItem::size() const {
291     return d_ptr->m_bytes;
292 }
293
294 QString FileItem::title() {
295     QFileInfo fInfo (filePath ());
296     return fInfo.fileName ();
297 }
298
299 QString FileItem::description() {
300     //TODO: Should we return something?
301     return QString();
302 }
303
304 QString FileItem::icon() {
305     return d_ptr->mimeIconName ();
306 }
307
308 bool FileItem::isReady () {
309     
310     bool retVal = false;
311     if ( d_ptr->m_bytes > 0.0 ) {
312         retVal =  true;
313     } else if (d_ptr->m_waitingForSize == true ) {
314         retVal =  false;
315     } else {
316         d_ptr->m_waitingForSize = true;
317         d_ptr->updateFromTracker();
318         if (d_ptr->m_bytes <= 0.0 ) {
319             QString className;
320             if (d_ptr->m_mime.contains("image", Qt::CaseInsensitive)) {
321                 className = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Image";
322             }
323             else if (d_ptr->m_mime.contains("video", Qt::CaseInsensitive)) {
324                 className = "http://www.tracker-project.org/temp/nmm#Video";
325             }
326             else if (d_ptr->m_mime.contains("audio", Qt::CaseInsensitive)) {
327                 className = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Audio";
328             }
329             else {
330                 WARN_STREAM << "Unable to create change notification for mime type" << d_ptr->m_mime;
331             }
332             if (!className.isEmpty()) {
333                 d_ptr->m_file_signaler = new TrackerChangeNotifier(className);
334             }
335             if(d_ptr->m_file_signaler != 0)
336             {
337                 DBG_STREAM << "Size not available yet, change notifier created";
338                 connect(d_ptr->m_file_signaler, 
339                     SIGNAL(changed(QList<TrackerChangeNotifier::Quad>,
340                                    QList<TrackerChangeNotifier::Quad>)),
341                     d_ptr,
342                     SLOT(fileChanged(QList<TrackerChangeNotifier::Quad>,
343                                  QList<TrackerChangeNotifier::Quad>)));
344             }
345             retVal =  false;
346         } else {
347             d_ptr->m_waitingForSize = false;
348             retVal =  true;
349         }
350     }
351     return retVal;
352 }
353
354 // -- private functions -------------------------------------------------------
355 FileItemPrivate::FileItemPrivate (FileItem * parent) : QObject (parent),
356     m_bytes (0), m_duration(0), m_trackerId(0),
357     m_file_signaler (0), m_parent (parent) {
358     
359     m_waitingForSize = false;
360 }
361
362 FileItemPrivate::~FileItemPrivate () {
363     if (m_file_signaler != 0 ) {
364         delete m_file_signaler;
365         m_file_signaler = 0;
366     }
367 }
368
369 QUrl FileItemPrivate::unencodedUrl (QUrl url) {
370     QByteArray array = url.toString().toUtf8();
371
372     return QUrl::fromEncoded (array);
373 }
374
375 void FileItemPrivate::updateFromTracker() {
376
377     DBG_STREAM << "Updating item data from tracker";
378
379     QSparqlQuery query(
380         "SELECT ?size ?created ?modified ?duration { "
381         "OPTIONAL { ?:u nie:byteSize ?size } "
382         "OPTIONAL { ?:u nie:contentCreated ?created } "
383         "OPTIONAL { ?:u nfo:fileLastModified ?modified } "
384         "OPTIONAL { ?:u nfo:duration ?duration } }");
385     query.bindValue("u", m_trackerIri);
386     QSparqlResult *result = m_sparqlConnection->exec(query);
387     result->waitForFinished();
388     if (!result->hasError()) {
389         if (result->next()) {
390             m_bytes = result->binding(0).value().toULongLong();
391             m_contentCreated = result->binding(1).value().toDateTime();
392             m_lastModified = result->binding(2).value().toDateTime();
393             m_duration = result->binding(3).value().toInt();
394         }
395     }
396     else {
397         DBG_STREAM << "Sparql query for item data failed:" <<
398             result->lastError().message();
399     }
400 }
401
402 void FileItemPrivate::fileChanged(QList<TrackerChangeNotifier::Quad> deletes,
403                                   QList<TrackerChangeNotifier::Quad> inserts) {
404     Q_UNUSED(deletes);
405
406     DBG_STREAM << "fileChanged signal from tracker";
407
408     bool match = false;
409     Q_FOREACH(const TrackerChangeNotifier::Quad &q, inserts) {
410         if (q.subject == m_trackerId) {
411             match = true;
412             break;
413         }
414     }
415
416     if (match) {
417         updateFromTracker();
418         if (m_bytes > 0.0 ) {
419             m_file_signaler->disconnect();
420             m_file_signaler->deleteLater();
421             m_file_signaler = 0;
422             m_waitingForSize = false;
423             Q_EMIT(ready());
424         }
425     }
426 }
427
428 QString FileItemPrivate::mimeIconName () {
429     ContentInfo cinfo = ContentInfo::forMime (m_mime);
430
431     if (cinfo.isValid () == false) {
432         return QLatin1String ("icon-m-content-file-unknown");
433     } else {
434         return cinfo.typeIcon ();
435     }
436 }
437