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