add more spacing
[personal-kdebase.git] / runtime / nepomuk / kioslaves / search / searchfolder.cpp
blob04af265c5137da6f6cafe50b92794350937828cc
1 /*
2 Copyright (C) 2008 by Sebastian Trueg <trueg at kde.org>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 #include "searchfolder.h"
21 #include "queryserviceclient.h"
23 #include <Soprano/Vocabulary/Xesam>
24 #include <Soprano/Vocabulary/NAO>
26 #include <Nepomuk/Variant>
28 #include <QtCore/QMutexLocker>
30 #include <KUrl>
31 #include <KDebug>
32 #include <KIO/Job>
33 #include <KIO/NetAccess>
34 #include <KUser>
35 #include <kdirnotify.h>
36 #include <qurlhash.h>
38 namespace {
39 QString addCounterToFileName( const QString& name, int i )
41 QString newName( name );
43 int start = name.lastIndexOf('.');
44 if (start != -1) {
45 // has a . somewhere, e.g. it has an extension
46 newName.insert(start, QString::number( i ));
48 else {
49 // no extension, just tack it on to the end
50 newName += QString::number( i );
52 return newName;
57 Nepomuk::SearchEntry::SearchEntry( const QUrl& res,
58 const KIO::UDSEntry& uds )
59 : m_resource( res ),
60 m_entry( uds )
65 Nepomuk::SearchFolder::SearchFolder()
67 // try to force clients to invalidate their cache
68 org::kde::KDirNotify::emitFilesAdded( "nepomuksearch:/" + m_name );
72 Nepomuk::SearchFolder::SearchFolder( const QString& name, const Search::Query& query, KIO::SlaveBase* slave )
73 : QThread(),
74 m_name( name ),
75 m_query( query ),
76 m_initialListingFinished( false ),
77 m_slave( slave ),
78 m_listEntries( false ),
79 m_statingStarted( false )
81 kDebug() << name << QThread::currentThread();
82 Q_ASSERT( !name.isEmpty() );
84 qRegisterMetaType<QList<QUrl> >();
88 Nepomuk::SearchFolder::~SearchFolder()
90 kDebug() << m_name << QThread::currentThread();
92 // properly shut down the search thread
93 quit();
94 wait();
96 qDeleteAll( m_entries );
100 void Nepomuk::SearchFolder::run()
102 kDebug() << m_name << QThread::currentThread();
104 m_client = new Nepomuk::Search::QueryServiceClient();
106 // results signals are connected directly to update the results cache m_results
107 // and the entries cache m_entries, as well as emitting KDirNotify signals
108 // a queued connection is not possible since we have no event loop after the
109 // initial listing which means that queued signals would never get delivered
110 connect( m_client, SIGNAL( newEntries( const QList<Nepomuk::Search::Result>& ) ),
111 this, SLOT( slotNewEntries( const QList<Nepomuk::Search::Result>& ) ),
112 Qt::DirectConnection );
113 connect( m_client, SIGNAL( entriesRemoved( const QList<QUrl>& ) ),
114 this, SLOT( slotEntriesRemoved( const QList<QUrl>& ) ),
115 Qt::DirectConnection );
117 // slotFinishedListing needs to be called in the GUi thread
118 connect( m_client, SIGNAL( finishedListing() ),
119 this, SLOT( slotFinishedListing() ),
120 Qt::QueuedConnection );
122 m_client->query( m_query );
123 exec();
124 delete m_client;
126 kDebug() << m_name << "done";
130 void Nepomuk::SearchFolder::list()
132 kDebug() << m_name << QThread::currentThread();
134 m_listEntries = !m_initialListingFinished;
135 m_statEntry = false;
137 if ( !isRunning() ) {
138 start();
140 else {
141 // list all cached entries
142 for ( QHash<QString, SearchEntry*>::const_iterator it = m_entries.constBegin();
143 it != m_entries.constEnd(); ++it ) {
144 m_slave->listEntry( ( *it )->entry(), false );
147 // if there is nothing more to list...
148 if ( m_initialListingFinished &&
149 m_results.isEmpty() ) {
150 m_slave->listEntry( KIO::UDSEntry(), true );
151 m_slave->finished();
153 else {
154 m_listEntries = true;
158 // if we have more to list
159 if ( m_listEntries ) {
160 if ( !m_statingStarted ) {
161 QTimer::singleShot( 0, this, SLOT( slotStatNextResult() ) );
163 kDebug() << "entering loop" << m_name << QThread::currentThread();
164 m_loop.exec();
169 void Nepomuk::SearchFolder::stat( const QString& name )
171 kDebug() << name;
173 if ( SearchEntry* entry = findEntry( name ) ) {
174 m_slave->statEntry( entry->entry() );
175 m_slave->finished();
177 else if ( !isRunning() ||
178 !m_results.isEmpty() ) {
179 m_nameToStat = name;
180 m_statEntry = true;
181 m_listEntries = false;
183 if ( !isRunning() ) {
184 start();
187 if ( !m_statingStarted ) {
188 QTimer::singleShot( 0, this, SLOT( slotStatNextResult() ) );
190 m_loop.exec();
192 else {
193 m_slave->error( KIO::ERR_DOES_NOT_EXIST, "nepomuksearch:/" + m_name + '/' + name );
198 Nepomuk::SearchEntry* Nepomuk::SearchFolder::findEntry( const QString& name ) const
200 kDebug() << name;
202 QHash<QString, SearchEntry*>::const_iterator it = m_entries.find( name );
203 if ( it != m_entries.end() ) {
204 kDebug() << "-----> found";
205 return *it;
207 else {
208 kDebug() << "-----> not found";
209 return 0;
214 Nepomuk::SearchEntry* Nepomuk::SearchFolder::findEntry( const KUrl& url ) const
216 // FIXME
217 return 0;
221 // always called in search thread
222 void Nepomuk::SearchFolder::slotNewEntries( const QList<Nepomuk::Search::Result>& results )
224 kDebug() << m_name << QThread::currentThread();
226 m_resultMutex.lock();
227 m_results += results;
228 m_resultMutex.unlock();
230 if ( m_initialListingFinished ) {
231 // inform everyone of the change
232 kDebug() << ( "Informing about change in folder nepomuksearch:/" + m_name );
233 org::kde::KDirNotify::emitFilesAdded( "nepomuksearch:/" + m_name );
238 // always called in search thread
239 void Nepomuk::SearchFolder::slotEntriesRemoved( const QList<QUrl>& entries )
241 kDebug() << QThread::currentThread();
243 QMutexLocker lock( &m_resultMutex );
245 foreach( const QUrl& uri, entries ) {
246 QHash<QUrl, QString>::iterator it = m_resourceNameMap.find( uri );
247 if ( it != m_resourceNameMap.end() ) {
248 delete m_entries.take( it.value() );
250 // inform everybody
251 org::kde::KDirNotify::emitFilesRemoved( QStringList() << ( "nepomuksearch:/" + m_name + '/' + *it ) );
253 m_resourceNameMap.erase( it );
259 // always called in search thread
260 void Nepomuk::SearchFolder::slotFinishedListing()
262 kDebug() << m_name << QThread::currentThread();
263 m_initialListingFinished = true;
264 wrap();
268 // always called in main thread
269 void Nepomuk::SearchFolder::slotStatNextResult()
271 // kDebug();
272 m_statingStarted = true;
274 while ( 1 ) {
275 // never lock the mutex for the whole duration of the method
276 // since it may start an event loop which can result in more
277 // newEntries signals to be delivered which would result in
278 // a deadlock
279 m_resultMutex.lock();
280 if( !m_results.isEmpty() ) {
281 Search::Result result = m_results.dequeue();
282 m_resultMutex.unlock();
283 SearchEntry* entry = statResult( result );
284 if ( entry ) {
285 if ( m_listEntries ) {
286 kDebug() << "listing" << entry->resource();
287 m_slave->listEntry( entry->entry(), false );
289 else if ( m_statEntry ) {
290 if ( m_nameToStat == entry->entry().stringValue( KIO::UDSEntry::UDS_NAME ) ) {
291 kDebug() << "stating" << entry->resource();
292 m_nameToStat = QString();
293 m_slave->statEntry( entry->entry() );
298 else {
299 m_resultMutex.unlock();
300 break;
304 if ( !m_results.isEmpty() ||
305 !m_initialListingFinished ) {
306 // we need to use the timer since statResource does only create an event loop
307 // for files, not for arbitrary resources.
308 QTimer::singleShot( 0, this, SLOT( slotStatNextResult() ) );
310 else {
311 m_statingStarted = false;
312 wrap();
317 // always called in main thread
318 void Nepomuk::SearchFolder::wrap()
320 kDebug() << m_name << QThread::currentThread();
322 if ( m_results.isEmpty() &&
323 m_initialListingFinished &&
324 m_loop.isRunning() ) {
325 if ( m_listEntries ) {
326 kDebug() << "listing done";
327 m_slave->listEntry( KIO::UDSEntry(), true );
328 m_slave->finished();
330 else if ( m_statEntry ) {
331 if ( !m_nameToStat.isEmpty() ) {
332 // if m_nameToStat is not empty the name was not found during listing which means that
333 // it does not exist
334 m_slave->error( KIO::ERR_DOES_NOT_EXIST, "nepomuksearch:/" + m_name + '/' + m_nameToStat );
335 m_nameToStat = QString();
337 else
338 m_slave->finished();
341 m_statingStarted = false;
342 m_listEntries = false;
343 m_statEntry = false;
344 kDebug() << m_name << QThread::currentThread() << "exiting loop";
345 m_loop.exit();
350 // always called in main thread
351 Nepomuk::SearchEntry* Nepomuk::SearchFolder::statResult( const Search::Result& result )
353 kDebug() << result.resourceUri();
355 KIO::UDSEntry uds;
357 KUrl url = result[Soprano::Vocabulary::Xesam::url()].toString();
358 if ( url.isEmpty() ) {
359 url = result.resourceUri();
361 bool isFile = false;
362 if ( !url.isEmpty() && url.scheme() != "akonadi" ) { // do not stat akonadi resouces here, way too slow, even hangs if akonadi is not running
363 kDebug() << "listing file" << url;
364 if ( KIO::StatJob* job = KIO::stat( url, KIO::HideProgressInfo ) ) {
365 job->setAutoDelete( false );
366 if ( KIO::NetAccess::synchronousRun( job, 0 ) ) {
367 uds = job->statResult();
368 if ( url.isLocalFile() ) {
369 uds.insert( KIO::UDSEntry::UDS_LOCAL_PATH, url.toLocalFile() );
371 isFile = true;
373 else {
374 kDebug() << "failed to stat" << url;
376 delete job;
381 // The nepomuk resource listing is the same as in the nepomuk kio slave.
382 // So either only depend on that or let the nepomuk kio slave fail on each
383 // stat. (the latter means that we need the nepomuk kio slave in kdebase)
385 if ( !isFile ) {
386 kDebug() << "listing resource" << result.resourceUri();
388 Nepomuk::Resource res( result.resourceUri() );
390 QString name = res.genericLabel();
392 // make sure name is not the URI (which is the fallback of genericLabel() and will lead to crashes in KDirModel)
393 if ( name.contains( '/' ) ) {
394 name = name.section( '/', -1 );
395 if ( name.isEmpty() )
396 name = res.resourceUri().fragment();
397 if ( name.isEmpty() )
398 name = res.resourceUri().toString().replace( '/', '_' );
401 uds.insert( KIO::UDSEntry::UDS_NAME, name );
402 uds.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, name );
404 QString icon = res.genericIcon();
405 if ( !icon.isEmpty() ) {
406 uds.insert( KIO::UDSEntry::UDS_ICON_NAME, icon );
409 uds.insert( KIO::UDSEntry::UDS_CREATION_TIME, res.property( Soprano::Vocabulary::NAO::created() ).toDateTime().toTime_t() );
411 uds.insert( KIO::UDSEntry::UDS_ACCESS, 0700 );
412 uds.insert( KIO::UDSEntry::UDS_USER, KUser().loginName() );
414 // uds.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
415 uds.insert( KIO::UDSEntry::UDS_MIME_TYPE, "application/x-nepomuk-resource" );
418 uds.insert( KIO::UDSEntry::UDS_TARGET_URL, result.resourceUri().toString() );
421 // make sure we have no duplicate names
423 QString name = uds.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME );
424 if ( name.isEmpty() ) {
425 name = uds.stringValue( KIO::UDSEntry::UDS_NAME );
428 // the name is empty if the resource could not be stated
429 if ( !name.isEmpty() ) {
430 int cnt = 0;
431 if ( m_nameCntHash.contains( name ) ) {
432 cnt = ++m_nameCntHash[name];
434 else {
435 cnt = m_nameCntHash[name] = 0;
437 if ( cnt >= 1 ) {
438 name = addCounterToFileName( name, cnt );
440 uds.insert( KIO::UDSEntry::UDS_NAME, name );
441 uds.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, name );
443 SearchEntry* entry = new SearchEntry( result.resourceUri(), uds );
444 m_entries.insert( name, entry );
445 m_resourceNameMap.insert( result.resourceUri(), name );
447 kDebug() << "Stating" << result.resourceUri() << "done";
449 return entry;
451 else {
452 // no valid name -> no valid uds
453 kDebug() << "Stating" << result.resourceUri() << "failed";
454 return 0;