1 #include "nepomukintegration.h"
2 
3 #include <QtCore/QCoreApplication>
4 #include <QtCore/QThread>
5 #include <QtCore/QtConcurrentRun>
6 #include <QtCore/QMutex>
7 #include <QtCore/QMutexLocker>
8 #include <QtCore/QFileInfo>
9 #include <QtDBus/QDBusMessage>
10 #include <QtDBus/QDBusConnection>
11 #include <QtDBus/QDBusInterface>
12 #include <QtDBus/QDBusReply>
13 #include <QtXml/QDomDocument>
14 
15 #include <QUrl>
16 
17 #include "global.h"
18 #include "tag.h"
19 #include "debugwindow.h"
20 #include "basketscene.h"
21 #include "notecontent.h"
22 
23 #include <Nepomuk/Resource>
24 #include <Nepomuk/ResourceManager>
25 #include <Nepomuk/Tag>
26 #include <Nepomuk/Variant>
27 
28 #include <Soprano/Vocabulary/RDF>
29 
30 //Generated by the onto2vocabularyclass tool
31 #include "nie.h"        //From nie.trig
32 #include "nfo.h"        //From nfo.trig
33 #include "pimo.h"       //From pimo.trig
34 
35 nepomukIntegration * nepomukIntegration::instance = NULL;
36 QMutex nepomukIntegration::instanceMutex;
37 
38 
39 /*
40  *                    Constructor / Cleanup
41  */
42 
nepomukIntegration(BasketScene * basket,int idleTime=15000)43 nepomukIntegration::nepomukIntegration(BasketScene * basket, int idleTime = 15000) : QObject()
44         , idleTime(idleTime), workerThread(this), cleanupTimer(this), mutex()
45         , basketList(), isDoingUpdate(true), requestedIndexList(), isCleaningupRequestedIndexes(false)
46 {
47     connect( this, SIGNAL(updateCompleted(QString, bool)),
48              this, SLOT(checkQueue()),
49              Qt::QueuedConnection
50              );
51     connect( &cleanupTimer, SIGNAL(timeout()),
52              this, SLOT(cleanup()),
53              Qt::QueuedConnection
54              );
55 
56     connect( &workerThread, SIGNAL(finished()),
57              this, SLOT(deleteLater()),
58              Qt::QueuedConnection
59              );
60     connect( &workerThread, SIGNAL(terminated()),
61              this, SLOT(deleteLater()),
62              Qt::QueuedConnection
63              );
64 
65     QMutexLocker locker(&mutex);
66     basketList << basket;
67 
68     if (!Nepomuk::ResourceManager::instance()->initialized()) {
69         int err = Nepomuk::ResourceManager::instance()->init(); //should be done before moving to thread
70         DEBUG_WIN << QString("\tNepomuk::ResourceManager::instance initialized, err=%1").arg(err);
71     }
72 
73     moveToThread(&workerThread);
74     QTimer::singleShot(500, this, SLOT(doUpdate()));
75     workerThread.start(QThread::IdlePriority);
76 
77     DEBUG_WIN << "nepomukIntegration object constructed";
78 }
79 
80 
cleanup()81 void nepomukIntegration::cleanup() {
82     DEBUG_WIN << "cleanup: starting";
83     QMutexLocker locker(&instanceMutex);
84     //Keep the timer active for a long time
85     cleanupTimer.start(idleTime*100);
86 
87     if ( ! basketList.isEmpty() ) {
88         if ( ! isDoingUpdate ) {
89             DEBUG_WIN << "<font color='red'>basketList is NOT empty but isDoingUpdate is false!</font>";
90             QTimer::singleShot(500, this, SLOT(checkQueue()));
91         }
92         DEBUG_WIN << "cleanup: deactivating cleanupTimer and returning.";
93         cleanupTimer.stop();
94         return;
95     }
96     if ( ! requestedIndexList.isEmpty() ) {
97         if ( ! isCleaningupRequestedIndexes ) {
98             DEBUG_WIN << "cleanup: requestedIndexList is NOT empty";
99             QDBusInterface * nepomukstrigiDBusInterface = new QDBusInterface( "org.kde.nepomuk.services.nepomukstrigiservice",
100                                                                               "/nepomukstrigiservice", "", QDBusConnection::sessionBus() , this
101                                                                               );
102             QDBusReply<bool> isIndexing = nepomukstrigiDBusInterface->call("isIndexing");
103             if ( isIndexing.isValid() && ! isIndexing ) {
104                 DEBUG_WIN << "cleanup: nepomukstrigiservice is NOT indexing, going to run cleanupRequestedIndexes.";
105                 QDBusConnection::sessionBus().disconnect(
106                         "org.kde.nepomuk.services.nepomukstrigiservice", "/nepomukstrigiservice", "", "indexingStopped",
107                         this, SLOT(cleanupRequestedIndexes())
108                         );
109                 cleanupRequestedIndexes();
110             } else if ( ! isDoingUpdate ) {
111                 DEBUG_WIN << "requestedIndexList is NOT empty, nepomukstrigiservice isIndexing is true and isDoingUpdate is false.";
112                 DEBUG_WIN << "Scheduling checkQueue.";
113                 QTimer::singleShot(500, this, SLOT(checkQueue()));
114             }
115         }
116         DEBUG_WIN << "cleanup: deactivating cleanupTimer and returning.";
117         cleanupTimer.stop();
118         return;
119     }
120     instance = NULL;
121     DEBUG_WIN << "nepomukIntegration instance is set to NULL";
122     workerThread.quit();
123     moveToThread(QCoreApplication::instance()->thread());
124 }
125 
126 
127 /*
128  *                    Queue baskets and indexRequests / checkQueue
129  */
130 
updateMetadata(BasketScene * basket)131 void nepomukIntegration::updateMetadata(BasketScene * basket) {
132     DEBUG_WIN << "updateMetadata: Going to lock updaterInstanceMutex";
133     QMutexLocker locker(&instanceMutex);
134     if ( instance == NULL ) {
135         instance = new nepomukIntegration(basket);
136     } else {
137         instance->queueBasket(basket);
138     }
139     DEBUG_WIN << "updateMetadata: Done";
140 }
141 
checkQueue()142 void nepomukIntegration::checkQueue() {
143     DEBUG_WIN << "checkQueue: Going to lock";
144     QMutexLocker locker(&mutex);
145     if ( basketList.isEmpty() ) {
146         if ( ! cleanupTimer.isActive() ) {
147             if ( requestedIndexList.isEmpty() ) {
148                 cleanupTimer.start(idleTime);
149                 DEBUG_WIN << "checkQueue: cleanupTimer started";
150             } else if ( ! isCleaningupRequestedIndexes ) {
151                 cleanupTimer.start(idleTime*10);
152                 DEBUG_WIN << "checkQueue: requestedIndexList is NOT empty, cleanupTimer started with an interval of idleTime x 10";
153             }
154         }
155         isDoingUpdate = false;
156     } else {
157         cleanupTimer.stop();
158         isDoingUpdate = true;
159         QTimer::singleShot(500, this, SLOT(doUpdate()));
160     }
161     DEBUG_WIN << "checkQueue: Done";
162 }
163 
queueBasket(BasketScene * basket)164 void nepomukIntegration::queueBasket(BasketScene * basket) {
165     DEBUG_WIN << "queueBasket: Going to lock";
166     QMutexLocker locker(&mutex);
167     basketList << basket;
168     if ( ! isDoingUpdate ) {
169         cleanupTimer.stop();
170         isDoingUpdate = true;
171         QTimer::singleShot(500, this, SLOT(doUpdate()));
172         DEBUG_WIN << "queueBasket : doUpdate scheduled.";
173     }
174     DEBUG_WIN << "queueBasket: Done";
175 }
176 
queueIndexRequest(KUrl file)177 void nepomukIntegration::queueIndexRequest(KUrl file) {
178     DEBUG_WIN << "queueIndexRequest: Going to lock";
179     QMutexLocker locker(&mutex);
180     if ( requestedIndexList.isEmpty() ) {
181         QDBusConnection::sessionBus().connect(
182                 "org.kde.nepomuk.services.nepomukstrigiservice", "/nepomukstrigiservice", "", "indexingStopped",
183                 this, SLOT(cleanupRequestedIndexes())
184                 );
185         isCleaningupRequestedIndexes = false;
186     }
187     //If there is another one of this file in the queue, do not add it again
188     QUrl indexedFile;
189     foreach (indexedFile, requestedIndexList) {
190         if ( indexedFile == file ) {
191             DEBUG_WIN << "queueIndexRequest: (duplicate) Done";
192             return;
193         }
194     }
195     requestedIndexList << file;
196     DEBUG_WIN << "queueIndexRequest: Done";
197 }
198 
199 
200 /*
201  *                    Dequeue / Process baskets and indexRequests
202  */
203 
cleanupRequestedIndexes()204 void nepomukIntegration::cleanupRequestedIndexes() {
205     DEBUG_WIN << "cleanupRequestedIndexes: Going to lock";
206     if ( ! mutex.tryLock() ) {
207         DEBUG_WIN << "<font color='red'>cleanupRequestedIndexes could NOT get the lock!</font>";
208         DEBUG_WIN << "<font color='red'>Scheduling for a later execution and Returning!</font>";
209         QTimer::singleShot(1000, this, SLOT(cleanupRequestedIndexes()));
210         return;
211     }
212     if ( requestedIndexList.isEmpty() ) {
213         QDBusConnection::sessionBus().disconnect(
214                 "org.kde.nepomuk.services.nepomukstrigiservice", "/nepomukstrigiservice", "", "indexingStopped",
215                 this, SLOT(cleanupRequestedIndexes())
216                 );
217         mutex.unlock();
218         isCleaningupRequestedIndexes = false;
219         DEBUG_WIN << "\tqueue is empty, desconnected from indexingStopped signal and isCleaningupRequestedIndexes set to false.";
220         if ( ! isDoingUpdate ) {
221             //Make sure the timer is not active so that the queues are rechecked
222             //             and timeout of the timer is reevaluated and restarted
223             cleanupTimer.stop();
224             QTimer::singleShot(500, this, SLOT(checkQueue()));
225         }
226         DEBUG_WIN << "cleanupRequestedIndexes: Done";
227         return;
228     }
229     isCleaningupRequestedIndexes = true;
230     QUrl indexedFile = requestedIndexList.takeFirst();
231     mutex.unlock();
232     DEBUG_WIN << "cleanupRequestedIndexes: unlocked";
233 
234     DEBUG_WIN << "cleanupRequestedIndexes: \tnote : " << indexedFile.pathOrUrl();
235     Nepomuk::Resource noteRes(indexedFile);
236     noteRes.setProperty( Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::PIMO::Note() );
237     noteRes.setProperty( Soprano::Vocabulary::NIE::mimeType(), "application/x-basket-item" );
238 
239     QTimer::singleShot(500, this, SLOT(cleanupRequestedIndexes()));
240     DEBUG_WIN << "cleanupRequestedIndexes: Done. Returning";
241 }
242 
listAllNotes(Note * note,QString basketFolderAbsolutePath,QList<QString> & noteFileList)243 void listAllNotes(Note * note, QString basketFolderAbsolutePath, QList<QString> & noteFileList) {
244     while (note) {
245         if ( note->isGroup() ) {
246             listAllNotes(note->firstChild(), basketFolderAbsolutePath, noteFileList);
247         } else if ( note->content()->useFile() ) {
248             noteFileList << basketFolderAbsolutePath + note->content()->fileName();
249         }
250         note = note->next();
251     }
252 }
253 
254 
doUpdate()255 void nepomukIntegration::doUpdate() {
256     DEBUG_WIN << "doUpdate: Going to lock";
257     mutex.lock();
258     if ( basketList.isEmpty() ) {
259         mutex.unlock();
260         DEBUG_WIN << "<font color='red'>doUpdate should not be run with an empty basketList! Unlocked and Returning!</font>";
261         return;
262     }
263     BasketScene * basket = basketList.takeFirst();
264     QString basketFolderName = basket->folderName();
265     QString basketFolderAbsolutePath = Global::basketsFolder() + basketFolderName;
266     QString basketName = basket->basketName();
267     //If there is another one of this basket item in the queue, that one will be processed later (instead of the current one)
268     BasketScene * tmpBasket;
269     foreach (tmpBasket, basketList) {
270         if ( basketFolderName == tmpBasket->folderName() ) {
271             mutex.unlock();
272             DEBUG_WIN << "doUpdate: \tDuplicate basket index update request, unlocked\n";
273             emit updateCompleted(basketFolderName, false);
274             return;
275         }
276     }
277     mutex.unlock();
278     DEBUG_WIN << "doUpdate: \tUnlocked";
279     QFileInfo basketDirInfo(basketFolderAbsolutePath);
280     if ( ! basketDirInfo.isDir() ) {
281         DEBUG_WIN << "<font color='red'>Global::basketsFolder() + basket->folderName() does not yield in a valid dir! Returning!</font>";
282         emit updateCompleted(basketFolderName, false);
283         return;
284     }
285 
286     if (basket->isEncrypted()) {
287         //If basket is encrypted, skip indexing it
288         emit updateCompleted(basketFolderName, false);
289         return;
290     }
291     DEBUG_WIN << "Indexing (" << basketFolderName << "): " << basketName ;
292     if (Nepomuk::ResourceManager::instance()->initialized()) {
293         QUrl basketUri = KUrl( basketFolderAbsolutePath + ".basket" );
294         /* Nepomuk::File basketRes(basketUri); */
295         Nepomuk::Resource basketRes(basketUri);
296         /*addType works better: basketRes.setProperty( Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::PIMO::Note() ); */
297         basketRes.addType( Soprano::Vocabulary::PIMO::Note() );
298         /* Added by Strigi, no need: basketRes.addType( Soprano::Vocabulary::NFO::FileDataObject() ); */
299         basketRes.setProperty( Soprano::Vocabulary::NIE::mimeType(), "application/x-basket-item" );
300         /* nfo:fileUrl is deprecated in favor of nie:url: basketRes.setProperty( Soprano::Vocabulary::NFO::fileUrl(), basketUri ); */
301         /* This is done internally anyway: basketRes.setProperty( Soprano::Vocabulary::NIE::url(), basketUri ); */
302         basketRes.setProperty( Soprano::Vocabulary::NFO::fileName(), ".basket" );
303         basketRes.setProperty( Soprano::Vocabulary::NIE::title(), basketName);
304         basketRes.setLabel( basketName );
305 
306         QList<Tag*> usedTagsList;
307         basket->listUsedTags(usedTagsList);
308         Tag * tmpTag;
309         Nepomuk::Tag basketTag;
310         QList<Nepomuk::Tag> tagList;
311         foreach (tmpTag, usedTagsList) {
312             basketTag = Nepomuk::Tag( tmpTag->name() );
313             basketTag.setLabel( tmpTag->name() );
314             tagList.append( basketTag );
315         }
316         basketRes.setTags( tagList );
317         DEBUG_WIN << "doUpdate: \tTags are set";
318         DEBUG_WIN << "doUpdate: \tQueuing note names:";
319 
320         QList<QString> basketFileList;
321         Note *note = basket->firstNote();
322         if (! note ) {
323             DEBUG_WIN << "doUpdate: \tHas NO notes!";
324         } else {
325             listAllNotes(note, basketFolderAbsolutePath, basketFileList);
326         }
327         basketFileList << basketFolderAbsolutePath + ".basket";
328         QString noteContentFile;
329         QUrl noteUrl;
330         foreach (noteContentFile, basketFileList) {
331             DEBUG_WIN << "doUpdate: \t  noteContentFile: " << noteContentFile;
332             noteUrl = KUrl( noteContentFile );
333             if ( noteUrl.isRelative() ) {
334                 noteUrl = KUrl( Global::basketsFolder() + "/" + noteContentFile );
335             }
336             if ( ! noteUrl.isValid() || ! noteUrl.isLocalFile()
337                 || ! QFile::exists(noteUrl.path()) || ! QFileInfo(noteUrl.path()).isFile() ) {
338                 DEBUG_WIN << "\tnote : <font color='red'>" + noteContentFile + " NOT indexed!</font>" ;
339                 continue;
340             }
341             Nepomuk::Resource noteRes(noteUrl);
342             noteRes.addType( Soprano::Vocabulary::PIMO::Note() );
343             noteRes.setProperty( Soprano::Vocabulary::NIE::mimeType(), "application/x-basket-item" );
344             noteRes.setProperty( Soprano::Vocabulary::PIMO::partOf(), basketRes );
345             //TODO
346             //Is this name OK?
347             noteRes.setProperty( Soprano::Vocabulary::NIE::title(), basketName + " (" + noteContentFile + ")" );
348             noteRes.setLabel( basketName + " (" + noteContentFile + ")" );
349 
350             queueIndexRequest( noteUrl );
351         }
352         DEBUG_WIN << "doUpdate: Note queuing completed, now going to request indexing from nepomukstrigiservice:";
353         QDBusMessage nepomukstrigiDBusMessage =
354                 QDBusMessage::createMethodCall( "org.kde.nepomuk.services.nepomukstrigiservice",
355                                                 "/nepomukstrigiservice", "", "indexFolder"
356                                                 );
357         DEBUG_WIN << "doUpdate: \tnepomukstrigiDBusMessage created";
358         QList<QVariant> indexingArgs;
359         indexingArgs.append(basketFolderAbsolutePath);
360         indexingArgs.append(false);
361         nepomukstrigiDBusMessage.setArguments(indexingArgs);
362         DEBUG_WIN << "doUpdate: \tnepomukstrigiDBusMessage prepared";
363         if ( QDBusConnection::sessionBus().send(nepomukstrigiDBusMessage) ) {
364             DEBUG_WIN << "doUpdate: \t\tindexing requested via DBus.";
365         } else {
366             DEBUG_WIN << "doUpdate: \t\tindexing request FAILED!";
367         }
368 
369         //DEBUG
370         /*
371         QHash<QUrl, Nepomuk::Variant> basketProperties = basketRes.properties();
372         DEBUG_WIN << "\tproperties : ";
373         for( QHash<QUrl, Nepomuk::Variant>::const_iterator it = basketProperties.constBegin();
374             it != basketProperties.constEnd(); ++it ) {
375           QUrl propertyUri = it.key();
376         Nepomuk::Variant value = it.value();
377         Nepomuk::Types::Class propertyType( propertyUri );
378         DEBUG_WIN << "\t" + propertyType.uri().toString() + "/"
379             + propertyType.label() + "/" + propertyType.name() + ": " + value.toString();
380         }
381         */
382     } else {
383         DEBUG_WIN << "\tNepomuk::ResourceManager::instance initialization failed!";
384         emit updateCompleted(basketFolderName, false);
385         return;
386     }
387     DEBUG_WIN << "doUpdate: \tDone";
388     emit updateCompleted(basketFolderName, true);
389     return;
390 }
391 
doDelete(const QString & fullPath)392 bool nepomukIntegration::doDelete(const QString &fullPath) {
393     DEBUG_WIN << "doDelete: going to lock";
394     instanceMutex.lock();
395     if ( instance != NULL ) {
396         DEBUG_WIN << "doDelete: cleanup any instance of " << fullPath;
397         QString basketsFolder = Global::basketsFolder();
398         int i;
399         for ( i=0; i<instance->basketList.count(); i++ ) {
400             if( basketsFolder + instance->basketList.at(i)->folderName() == fullPath ) {
401                 instance->basketList.removeAt(i);
402             }
403         }
404         instance->requestedIndexList.removeAll(KUrl(fullPath));
405     }
406     instanceMutex.unlock();
407 
408     if (Nepomuk::ResourceManager::instance()->initialized()) {
409         QUrl basketUri = KUrl( fullPath );
410         Nepomuk::Resource basketRes(basketUri);
411         basketRes.remove();
412     } else {
413         DEBUG_WIN << "\tNepomuk::ResourceManager::instance initialization failed!";
414         return false;
415     }
416     DEBUG_WIN << "doDelete: Done";
417     return true;
418 }
419 
deleteMetadata(const QString & fullPath)420 void nepomukIntegration::deleteMetadata(const QString &fullPath) {
421     //Only process files in Global::basketsFolder()
422     if ( fullPath.contains(Global::basketsFolder()) )
423         QtConcurrent::run(doDelete, fullPath);
424 }
425