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)
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>
33 #include <KIO/NetAccess>
35 #include <kdirnotify.h>
39 QString
addCounterToFileName( const QString
& name
, int i
)
41 QString
newName( name
);
43 int start
= name
.lastIndexOf('.');
45 // has a . somewhere, e.g. it has an extension
46 newName
.insert(start
, QString::number( i
));
49 // no extension, just tack it on to the end
50 newName
+= QString::number( i
);
57 Nepomuk::SearchEntry::SearchEntry( const QUrl
& res
,
58 const KIO::UDSEntry
& 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
)
76 m_initialListingFinished( false ),
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
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
);
126 kDebug() << m_name
<< "done";
130 void Nepomuk::SearchFolder::list()
132 kDebug() << m_name
<< QThread::currentThread();
134 m_listEntries
= !m_initialListingFinished
;
137 if ( !isRunning() ) {
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 );
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();
169 void Nepomuk::SearchFolder::stat( const QString
& name
)
173 if ( SearchEntry
* entry
= findEntry( name
) ) {
174 m_slave
->statEntry( entry
->entry() );
177 else if ( !isRunning() ||
178 !m_results
.isEmpty() ) {
181 m_listEntries
= false;
183 if ( !isRunning() ) {
187 if ( !m_statingStarted
) {
188 QTimer::singleShot( 0, this, SLOT( slotStatNextResult() ) );
193 m_slave
->error( KIO::ERR_DOES_NOT_EXIST
, "nepomuksearch:/" + m_name
+ '/' + name
);
198 Nepomuk::SearchEntry
* Nepomuk::SearchFolder::findEntry( const QString
& name
) const
202 QHash
<QString
, SearchEntry
*>::const_iterator it
= m_entries
.find( name
);
203 if ( it
!= m_entries
.end() ) {
204 kDebug() << "-----> found";
208 kDebug() << "-----> not found";
214 Nepomuk::SearchEntry
* Nepomuk::SearchFolder::findEntry( const KUrl
& url
) const
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() );
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;
268 // always called in main thread
269 void Nepomuk::SearchFolder::slotStatNextResult()
272 m_statingStarted
= true;
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
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
);
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() );
299 m_resultMutex
.unlock();
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() ) );
311 m_statingStarted
= false;
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 );
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
334 m_slave
->error( KIO::ERR_DOES_NOT_EXIST
, "nepomuksearch:/" + m_name
+ '/' + m_nameToStat
);
335 m_nameToStat
= QString();
341 m_statingStarted
= false;
342 m_listEntries
= false;
344 kDebug() << m_name
<< QThread::currentThread() << "exiting loop";
350 // always called in main thread
351 Nepomuk::SearchEntry
* Nepomuk::SearchFolder::statResult( const Search::Result
& result
)
353 kDebug() << result
.resourceUri();
357 KUrl url
= result
[Soprano::Vocabulary::Xesam::url()].toString();
358 if ( url
.isEmpty() ) {
359 url
= result
.resourceUri();
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() );
374 kDebug() << "failed to stat" << url
;
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)
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() ) {
431 if ( m_nameCntHash
.contains( name
) ) {
432 cnt
= ++m_nameCntHash
[name
];
435 cnt
= m_nameCntHash
[name
] = 0;
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";
452 // no valid name -> no valid uds
453 kDebug() << "Stating" << result
.resourceUri() << "failed";