Changes: Move cache functions to central place and use for avatar cache
[qtcontacts-tracker:qtcontacts-tracker.git] / src / dao / classhierarchy.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 "classhierarchy.h"
43 #include "classhierarchymodel.h"
44 #include "logger.h"
45 #include "support.h"
46
47 #include <dbus/globalmutex.h>
48
49 #include <QtTracker/ontologies/tracker.h>
50 #include <QtTracker/tracker.h>
51
52 #include <errno.h>
53 #include <sys/stat.h>
54 #include <utime.h>
55
56 using namespace SopranoLive;
57 using namespace SopranoLive::Ontologies;
58
59 ///////////////////////////////////////////////////////////////////////////////////////////////////
60
61 class QTrackerClassHierarchyData : public QSharedData
62 {
63     Q_DISABLE_COPY(QTrackerClassHierarchyData);
64     friend class QTrackerClassHierarchy;
65
66 public:
67     static const QByteArray CacheVersion;
68     static const QLatin1String MutexName;
69
70     QTrackerClassHierarchyData(int mutexTimeout);
71
72     bool load();
73
74 private:
75     void addResource(const QUrl &iri, int id, int baseId);
76
77     bool mustRebuild() const;
78     bool readFromCache();
79     bool writeToCache();
80     bool rebuildCache();
81
82 private:
83     QMultiHash<int, int> m_baseClasses;
84     QHash<QUrl, int> m_classesByIri;
85     QHash<int, QUrl> m_classesById;
86     QSet<QUrl> m_missingClasses;
87     QSet<QUrl> m_extraClasses;
88     int m_mutexTimeout;
89 };
90
91 const QByteArray QTrackerClassHierarchyData::CacheVersion
92         ("com.nokia.qt.contacts.tracker.ClassHierarchyCache;1");
93 const QLatin1String QTrackerClassHierarchyData::MutexName
94         ("com.nokia.qt.contacts.tracker.ClassHierarchyCache");
95
96 QTrackerClassHierarchyData::QTrackerClassHierarchyData(int mutexTimeout)
97     : m_mutexTimeout(mutexTimeout)
98 {
99 }
100
101 bool
102 QTrackerClassHierarchyData::load()
103 {
104     if (not m_classesByIri.isEmpty() && m_missingClasses.isEmpty()) {
105         return true;
106     }
107
108     for(int i = 0; i < 10; ++i) {
109         if (readFromCache()) {
110             return true;
111         }
112
113         if (not rebuildCache()) {
114             qctWarn("Cannot rebuild RDF class cache. Waiting for 5 seconds.");
115             sleep(5);
116         }
117     }
118
119     return false;
120 }
121
122 void
123 QTrackerClassHierarchyData::addResource(const QUrl &classIri, int classId, int baseClassId)
124 {
125     // This method simply exists to prevent g++ from consuming insane amounts
126     // of memory and time for resolving templates.
127
128     m_classesByIri.insert(classIri, classId);
129     m_classesById.insert(classId, classIri);
130
131     if (0 != baseClassId) {
132         m_baseClasses.insertMulti(classId, baseClassId);
133     }
134 }
135
136 static QString
137 cacheFileName()
138 {
139     return qctContactsCacheDir().filePath(QLatin1String("rdf-classes.db"));
140 }
141
142 static QString
143 referenceFileName()
144 {
145     return QLatin1String("/usr/share/tracker/ontologies/11-rdf.ontology");
146 }
147
148 bool
149 QTrackerClassHierarchyData::mustRebuild() const
150 {
151     // we must rebuild if we already fetched data, and still have missing classes
152     if (not m_classesByIri.isEmpty() && not m_missingClasses.isEmpty()) {
153         return true;
154     }
155
156     QFileInfo cacheFile = cacheFileName();
157
158     // we must rebuild if the cache file doesn't exist yet
159     if (not cacheFile.exists()) {
160         return true;
161     }
162
163     QFileInfo referenceFile = referenceFileName();
164
165     // we only warn if the reference file doesn't exist yet to avoid infinite loops
166     if (not referenceFile.exists()) {
167         qctFail(QString::fromLatin1("Cannot find RDF ontology file %1. Aborting.").
168                 arg(referenceFile.fileName()));
169     }
170
171     // we must rebuild if reference file is newer than the cache
172     return referenceFile.lastModified() > cacheFile.lastModified();
173 }
174
175 bool
176 QTrackerClassHierarchyData::readFromCache()
177 {
178     if (mustRebuild()) {
179         return false;
180     }
181
182     QFile file(cacheFileName());
183
184     if (not file.open(QFile::ReadOnly)) {
185         qctWarn(QString::fromLatin1("Cannot read %1: %2").
186                 arg(file.fileName(), file.errorString()));
187         return false;
188     }
189
190     QDataStream in(&file);
191     QByteArray cacheVersion;
192
193     // check if the cache has the expected file format
194     in >> cacheVersion;
195
196     if (cacheVersion == CacheVersion) {
197         // read back the cache when version numbers match
198         in >> m_classesByIri;
199         in >> m_classesById;
200         in >> m_baseClasses;
201         in >> m_extraClasses;
202     } else {
203         m_classesByIri.clear();
204         m_classesById.clear();
205         m_baseClasses.clear();
206         m_extraClasses.clear();
207     }
208
209     // remove invalid class cache files
210     if (m_classesByIri.count() <= 1) {
211         qctWarn(QString::fromLatin1("Invalid class cache in %1").arg(file.fileName()));
212
213         QctGlobalMutex mutex(MutexName);
214         qctWarn("Will remove invalid class cache, waiting for mutex");
215
216         mutex.lockAndWait(m_mutexTimeout);
217         file.remove();
218
219         return false;
220     }
221
222     // remove those classes we already found in cache
223     m_missingClasses -= m_extraClasses;
224
225     return true;
226 }
227
228 static int
229 touch(const char *refpath, const char *dstpath)
230 {
231     struct stat refstat;
232
233     if (0 != stat(refpath, &refstat)) {
234         qctWarn(QString::fromLatin1("Cannot lookup timestamps for %1: %2").
235                 arg(refpath, strerror(errno)));
236         return -1;
237     }
238
239     const struct utimbuf timestamps = {
240         refstat.st_atime, refstat.st_mtime
241     };
242
243     if (0 != utime(dstpath, &timestamps)) {
244         qctWarn(QString::fromLatin1("Cannot update timestamps for %1: %2").
245                 arg(dstpath, strerror(errno)));
246         return -1;
247     }
248
249     return 0;
250 }
251
252 static bool
253 transferTimestamps(const QString &referenceFileName, const QString &targetFileName)
254 {
255     // modify timestamps via POSIX API as QFileInfo::setLastModified() is missing
256     return (0 == touch(qPrintable(referenceFileName), qPrintable(targetFileName)));
257 }
258
259 bool
260 QTrackerClassHierarchyData::writeToCache()
261 {
262     if (not qctContactsCacheDir().mkpath(QLatin1String("."))) {
263         qctWarn(QString::fromLatin1("Cannot create class cache folder %1: %2").
264                 arg(qctContactsCacheDir().path(), strerror(errno)));
265         return false;
266     }
267
268     QFile cacheFile(cacheFileName());
269
270     if (not cacheFile.open(QFile::WriteOnly)) {
271         qctWarn(QString::fromLatin1("Cannot write %1: %2").
272                 arg(cacheFile.fileName(), cacheFile.errorString()));
273         return false;
274     }
275
276     // m_missingClasses still contains the newly added extra classes
277     const QSet<QUrl> explicitClasses = m_extraClasses + m_missingClasses;
278
279     qDebug(PACKAGE ": writing %s", qPrintable(cacheFile.fileName()));
280     QDataStream out(&cacheFile);
281
282     out << CacheVersion; // MUST be updated when the file format is changed!
283     out << m_classesByIri;
284     out << m_classesById;
285     out << m_baseClasses;
286     out << explicitClasses;
287
288     cacheFile.flush(); // not checking result, let's assume it worked
289     cacheFile.close();
290
291     // Set timestamps of the cache file to those of the reference file as the system clock
292     // might be set improperly, which would break cacheUpdateNeeded(). See NB#182612.
293     return transferTimestamps(referenceFileName(), cacheFile.fileName());
294 }
295
296 bool
297 QTrackerClassHierarchyData::rebuildCache()
298 {
299     QctGlobalMutex mutex(MutexName);
300     qctWarn("Must rebuild class cache, waiting for mutex");
301     mutex.lockAndWait(m_mutexTimeout);
302
303     if (not mustRebuild()) {
304         qctWarn("Class cache already updated by other process");
305         mutex.unlock();
306         return true;
307     }
308
309     m_classesByIri.clear();
310     m_classesById.clear();
311     m_baseClasses.clear();
312
313     QTrackerClassHierarchyModel model(m_missingClasses + m_extraClasses);
314
315     if (not model.moveNext()) {
316         qctWarn("Empty response from tracker, must try again");
317         return false;
318     }
319
320     do {
321         addResource(model.classIri(), model.classId(), model.baseClassId());
322     } while(model.moveNext());
323
324     if (model.hasError()) {
325         qctWarn(QString::fromLatin1("Error when fetching class information, must try again: %1").
326                 arg(model.lastErrorMessage()));
327         return false;
328     }
329
330     if (not writeToCache()) {
331         return false;
332     }
333
334     m_extraClasses += m_missingClasses;
335     m_missingClasses.clear();
336
337     return true;
338 }
339
340 ///////////////////////////////////////////////////////////////////////////////////////////////////
341
342 QTrackerClassHierarchy::QTrackerClassHierarchy(const QTrackerClassHierarchy &other)
343     : d(other.d)
344 {
345 }
346
347 QTrackerClassHierarchy &
348 QTrackerClassHierarchy::operator=(const QTrackerClassHierarchy &other)
349 {
350     return d = other.d, *this;
351 }
352
353 ///////////////////////////////////////////////////////////////////////////////////////////////////
354
355 QTrackerClassHierarchy::QTrackerClassHierarchy(int mutexTimeout)
356     : d(new QTrackerClassHierarchyData(mutexTimeout))
357 {
358 }
359
360 QTrackerClassHierarchy::~QTrackerClassHierarchy()
361 {
362 }
363
364 ///////////////////////////////////////////////////////////////////////////////////////////////////
365
366 void
367 QTrackerClassHierarchy::setMutexTimeout(int timeout)
368 {
369     d->m_mutexTimeout = timeout;
370 }
371
372 int
373 QTrackerClassHierarchy::mutexTimeout() const
374 {
375     return d->m_mutexTimeout;
376 }
377
378 ///////////////////////////////////////////////////////////////////////////////////////////////////
379
380 bool
381 QTrackerClassHierarchy::readClassIds()
382 {
383     return d->load();
384 }
385
386 void
387 QTrackerClassHierarchy::add(const QUrl & iri)
388 {
389     if (not d->m_extraClasses.contains(iri)) {
390         d->m_missingClasses.insert(iri);
391     }
392 }
393
394 int
395 QTrackerClassHierarchy::getId(const QUrl & iri) const
396 {
397     return d->m_classesByIri.value(iri);
398 }
399
400 QUrl
401 QTrackerClassHierarchy::getIri(int id) const
402 {
403     return d->m_classesById.value(id);
404 }
405
406 bool
407 QTrackerClassHierarchy::isSubClassOf(const QUrl &subClassIri, const QUrl &baseClassIri) const
408 {
409     return isSubClassOf(getId(subClassIri), getId(baseClassIri));
410 }
411
412 bool
413 QTrackerClassHierarchy::isSubClassOf(int subClassId, int baseClassId) const
414 {
415     if (baseClassId == subClassId) {
416         return true;
417     }
418
419     QMultiHash<int, int>::ConstIterator i = d->m_baseClasses.find(subClassId);
420     const QMultiHash<int, int>::ConstIterator end = d->m_baseClasses.end();
421
422     for (; i != end && i.key() == subClassId; ++i) {
423         if (isSubClassOf(*i, baseClassId)) {
424             return true;
425         }
426     }
427
428     return false;
429 }
430
431 static bool
432 matchesOntologies(const QUrl &classIri, const QStringList &ontologyPrefixes)
433 {
434     foreach(const QString &prefix, ontologyPrefixes) {
435         if (classIri.toString().startsWith(prefix)) {
436             return true;
437         }
438     }
439
440     return ontologyPrefixes.isEmpty();
441 }
442
443 QSet<QUrl>
444 QTrackerClassHierarchy::inheritedClasses(const QUrl &classIri,
445                                          const QStringList &ontologyPrefixes) const
446 {
447     if (not matchesOntologies(classIri, ontologyPrefixes)) {
448         return QSet<QUrl>();
449     }
450
451     QSet<QUrl> result;
452     result.insert(classIri);
453
454     // scan base classes
455     const int classId = getId(classIri);
456
457     if (0 != classId) {
458         QMultiHash<int, int>::ConstIterator i = d->m_baseClasses.find(classId);
459         const QMultiHash<int, int>::ConstIterator end = d->m_baseClasses.end();
460
461         for (; i != end && i.key() == classId; ++i) {
462             result += inheritedClasses(getIri(*i), ontologyPrefixes);
463         }
464     }
465
466     // report result
467     return result;
468 }