diff --git a/kioslaves/search/searchfolder.cpp b/kioslaves/search/searchfolder.cpp index cf6ef0bd..14bfec94 100644 --- a/kioslaves/search/searchfolder.cpp +++ b/kioslaves/search/searchfolder.cpp @@ -1,553 +1,538 @@ /* Copyright (C) 2008 by Sebastian Trueg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "searchfolder.h" #include "nfo.h" #include "nie.h" #include "pimo.h" #include "queryserviceclient.h" #include #include #include // for qHash( QUrl ) #include #include #include #include #include #include #include #include #include #include namespace { QString addCounterToFileName( const QString& name, int i ) { QString newName( name ); int start = name.lastIndexOf('.'); if (start != -1) { // has a . somewhere, e.g. it has an extension newName.insert(start, QString::number( i )); } else { // no extension, just tack it on to the end newName += QString::number( i ); } return newName; } } Nepomuk::SearchEntry::SearchEntry( const QUrl& res, const KIO::UDSEntry& uds ) : m_resource( res ), m_entry( uds ) { } Nepomuk::SearchFolder::SearchFolder() { // try to force clients to invalidate their cache org::kde::KDirNotify::emitFilesAdded( "nepomuksearch:/" + m_name ); } Nepomuk::SearchFolder::SearchFolder( const QString& name, const Search::Query& query, KIO::SlaveBase* slave ) : QThread(), m_name( name ), m_query( query ), m_initialListingFinished( false ), m_slave( slave ), m_listEntries( false ), m_statingStarted( false ) { kDebug() << name << QThread::currentThread(); Q_ASSERT( !name.isEmpty() ); qRegisterMetaType >(); } Nepomuk::SearchFolder::~SearchFolder() { kDebug() << m_name << QThread::currentThread(); // properly shut down the search thread quit(); wait(); qDeleteAll( m_entries ); } void Nepomuk::SearchFolder::run() { kDebug() << m_name << QThread::currentThread(); m_client = new Nepomuk::Search::QueryServiceClient(); - // results signals are connected directly to update the results cache m_results + // results signals are connected directly to update the results cache m_resultsQueue // and the entries cache m_entries, as well as emitting KDirNotify signals // a queued connection is not possible since we have no event loop after the // initial listing which means that queued signals would never get delivered connect( m_client, SIGNAL( newEntries( const QList& ) ), this, SLOT( slotNewEntries( const QList& ) ), Qt::DirectConnection ); connect( m_client, SIGNAL( entriesRemoved( const QList& ) ), this, SLOT( slotEntriesRemoved( const QList& ) ), Qt::DirectConnection ); // slotFinishedListing needs to be called in the GUi thread connect( m_client, SIGNAL( finishedListing() ), this, SLOT( slotFinishedListing() ), Qt::QueuedConnection ); m_client->query( m_query ); exec(); delete m_client; kDebug() << m_name << "done"; } void Nepomuk::SearchFolder::list() { kDebug() << m_name << QThread::currentThread(); m_listEntries = !m_initialListingFinished; - m_statEntry = false; if ( !isRunning() ) { start(); } else { // list all cached entries for ( QHash::const_iterator it = m_entries.constBegin(); it != m_entries.constEnd(); ++it ) { m_slave->listEntry( ( *it )->entry(), false ); } // if there is nothing more to list... if ( m_initialListingFinished && - m_results.isEmpty() ) { + m_resultsQueue.isEmpty() ) { m_slave->listEntry( KIO::UDSEntry(), true ); m_slave->finished(); } else { m_listEntries = true; } } // if we have more to list if ( m_listEntries ) { if ( !m_statingStarted ) { QTimer::singleShot( 0, this, SLOT( slotStatNextResult() ) ); } kDebug() << "entering loop" << m_name << QThread::currentThread(); m_loop.exec(); } } void Nepomuk::SearchFolder::stat( const QString& name ) { kDebug() << name; if ( SearchEntry* entry = findEntry( name ) ) { m_slave->statEntry( entry->entry() ); m_slave->finished(); } - else if ( !isRunning() || - !m_results.isEmpty() ) { - m_nameToStat = name; - m_statEntry = true; - m_listEntries = false; - - if ( !isRunning() ) { - start(); - } - - if ( !m_statingStarted ) { - QTimer::singleShot( 0, this, SLOT( slotStatNextResult() ) ); - } - m_loop.exec(); - } else { m_slave->error( KIO::ERR_DOES_NOT_EXIST, "nepomuksearch:/" + m_name + '/' + name ); } } -Nepomuk::SearchEntry* Nepomuk::SearchFolder::findEntry( const QString& name ) const +Nepomuk::SearchEntry* Nepomuk::SearchFolder::findEntry( const QString& name ) { kDebug() << name; + // + // get all results in case we do not have them already + // + if ( !isRunning() || + !m_resultsQueue.isEmpty() ) { + + if ( !isRunning() ) + start(); + + if ( !m_statingStarted ) { + m_listEntries = false; + QTimer::singleShot( 0, this, SLOT( slotStatNextResult() ) ); + } + kDebug() << "entering loop" << m_name << QThread::currentThread(); + m_loop.exec(); + } + + // + // search for the one we need + // QHash::const_iterator it = m_entries.find( name ); if ( it != m_entries.end() ) { kDebug() << "-----> found"; return *it; } else { kDebug() << "-----> not found"; return 0; } } -Nepomuk::SearchEntry* Nepomuk::SearchFolder::findEntry( const KUrl& url ) const +Nepomuk::SearchEntry* Nepomuk::SearchFolder::findEntry( const KUrl& url ) { // FIXME return 0; } // always called in search thread void Nepomuk::SearchFolder::slotNewEntries( const QList& results ) { kDebug() << m_name << QThread::currentThread(); m_resultMutex.lock(); - m_results += results; + m_resultsQueue += results; m_resultMutex.unlock(); if ( m_initialListingFinished ) { // inform everyone of the change kDebug() << ( "Informing about change in folder nepomuksearch:/" + m_name ); org::kde::KDirNotify::emitFilesAdded( "nepomuksearch:/" + m_name ); } } // always called in search thread void Nepomuk::SearchFolder::slotEntriesRemoved( const QList& entries ) { kDebug() << QThread::currentThread(); QMutexLocker lock( &m_resultMutex ); foreach( const QUrl& uri, entries ) { QHash::iterator it = m_resourceNameMap.find( uri ); if ( it != m_resourceNameMap.end() ) { delete m_entries.take( it.value() ); // inform everybody org::kde::KDirNotify::emitFilesRemoved( QStringList() << ( "nepomuksearch:/" + m_name + '/' + *it ) ); m_resourceNameMap.erase( it ); } } } // always called in search thread void Nepomuk::SearchFolder::slotFinishedListing() { kDebug() << m_name << QThread::currentThread(); m_initialListingFinished = true; wrap(); } // always called in main thread void Nepomuk::SearchFolder::slotStatNextResult() { // kDebug(); m_statingStarted = true; while ( 1 ) { // never lock the mutex for the whole duration of the method // since it may start an event loop which can result in more // newEntries signals to be delivered which would result in // a deadlock m_resultMutex.lock(); - if( !m_results.isEmpty() ) { - Search::Result result = m_results.dequeue(); + if( !m_resultsQueue.isEmpty() ) { + Search::Result result = m_resultsQueue.dequeue(); m_resultMutex.unlock(); SearchEntry* entry = statResult( result ); if ( entry ) { if ( m_listEntries ) { kDebug() << "listing" << entry->resource(); m_slave->listEntry( entry->entry(), false ); } - else if ( m_statEntry ) { - if ( m_nameToStat == entry->entry().stringValue( KIO::UDSEntry::UDS_NAME ) ) { - kDebug() << "stating" << entry->resource(); - m_nameToStat.clear(); - m_slave->statEntry( entry->entry() ); - } - } } } else { m_resultMutex.unlock(); break; } } - if ( !m_results.isEmpty() || + if ( !m_resultsQueue.isEmpty() || !m_initialListingFinished ) { - // we need to use the timer since statResource does only create an event loop - // for files, not for arbitrary resources. QTimer::singleShot( 0, this, SLOT( slotStatNextResult() ) ); } else { m_statingStarted = false; wrap(); } } // always called in main thread void Nepomuk::SearchFolder::wrap() { kDebug() << m_name << QThread::currentThread(); - if ( m_results.isEmpty() && + if ( m_resultsQueue.isEmpty() && m_initialListingFinished && m_loop.isRunning() ) { if ( m_listEntries ) { kDebug() << "listing done"; m_slave->listEntry( KIO::UDSEntry(), true ); - m_slave->finished(); - } - else if ( m_statEntry ) { - if ( !m_nameToStat.isEmpty() ) { - // if m_nameToStat is not empty the name was not found during listing which means that - // it does not exist - m_slave->error( KIO::ERR_DOES_NOT_EXIST, "nepomuksearch:/" + m_name + '/' + m_nameToStat ); - m_nameToStat.clear(); - } - else - m_slave->finished(); } + m_slave->finished(); + m_statingStarted = false; m_listEntries = false; - m_statEntry = false; kDebug() << m_name << QThread::currentThread() << "exiting loop"; m_loop.exit(); } } namespace { /** * Stat a file. * * \param url The url of the file * \param success will be set to \p true if the stat was successful */ KIO::UDSEntry statFile( const KUrl& url, bool& success ) { success = false; KIO::UDSEntry uds; if ( !url.isEmpty() && url.scheme() != "akonadi" && url.scheme() != "nepomuk" ) { // do not stat akonadi resouces here, way too slow, even hangs if akonadi is not running kDebug() << "listing file" << url; if ( KIO::StatJob* job = KIO::stat( url, KIO::HideProgressInfo ) ) { job->setAutoDelete( false ); if ( KIO::NetAccess::synchronousRun( job, 0 ) ) { uds = job->statResult(); if ( url.isLocalFile() ) { uds.insert( KIO::UDSEntry::UDS_LOCAL_PATH, url.toLocalFile() ); } success = true; } else { kDebug() << "failed to stat" << url; } delete job; } } return uds; } /** * Workaround a missing Nepomuk::Variant feature which is in trunk but not in 4.3.0. */ QUrl extractUrl( const Nepomuk::Variant& v ) { QList rl = v.toResourceList(); if ( !rl.isEmpty() ) return rl.first().resourceUri(); return QUrl(); } } // always called in main thread Nepomuk::SearchEntry* Nepomuk::SearchFolder::statResult( const Search::Result& result ) { kDebug() << result.resourceUri(); // // First we check if the resource is a file itself. For that we first get // the URL (being backwards compatible with Xesam data) and then stat that URL // KUrl url = result[Nepomuk::Vocabulary::NIE::url()].uri(); if ( url.isEmpty() ) { url = result[Soprano::Vocabulary::Xesam::url()].uri(); if ( url.isEmpty() ) url = result.resourceUri(); } bool isFile = false; KIO::UDSEntry uds = statFile( url, isFile ); // // If it is not a file we get inventive: // In case it is a pimo thing, we see if it has a grounding occurrence // which is a file. If so, we merge the two by taking the file URL and the thing's // label and icon if set // if ( !isFile ) { kDebug() << "listing resource" << result.resourceUri(); // // We only create a resource here since this is a rather slow process and // a lot of file results could become slow then. // Nepomuk::Resource res( result.resourceUri() ); // // let's see if it is a pimo thing which refers to a file // bool isPimoThingLinkedFile = false; if ( res.pimoThing() == res ) { if ( !res.pimoThing().groundingOccurrences().isEmpty() ) { Nepomuk::Resource fileRes = res.pimoThing().groundingOccurrences().first(); url = extractUrl( fileRes.property( Nepomuk::Vocabulary::NIE::url() ) ); if ( url.isEmpty() ) { url = extractUrl( fileRes.property( Soprano::Vocabulary::Xesam::url() ) ); if ( url.isEmpty() ) url = result.resourceUri(); } uds = statFile( url, isPimoThingLinkedFile ); } } QString name = res.label(); if ( name.isEmpty() && !isPimoThingLinkedFile ) name = res.genericLabel(); // make sure name is not the URI (which is the fallback of genericLabel() and will lead to crashes in KDirModel) if ( name.contains( '/' ) ) { name = name.section( '/', -1 ); if ( name.isEmpty() ) name = res.resourceUri().fragment(); if ( name.isEmpty() ) name = res.resourceUri().toString().replace( '/', '_' ); } // // We always use the pimo things label, even if it points to a file // if ( !name.isEmpty() ) { uds.insert( KIO::UDSEntry::UDS_NAME, name ); uds.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, name ); } // // An icon set on the pimo thing overrides the file's icon // QString icon = res.genericIcon(); if ( !icon.isEmpty() ) { uds.insert( KIO::UDSEntry::UDS_ICON_NAME, icon ); } else if ( !isPimoThingLinkedFile ) { uds.insert( KIO::UDSEntry::UDS_ICON_NAME, "nepomuk" ); } // // Generate some dummy values // if ( !isPimoThingLinkedFile ) { uds.insert( KIO::UDSEntry::UDS_CREATION_TIME, res.property( Soprano::Vocabulary::NAO::created() ).toDateTime().toTime_t() ); uds.insert( KIO::UDSEntry::UDS_ACCESS, 0700 ); uds.insert( KIO::UDSEntry::UDS_USER, KUser().loginName() ); // uds.insert( KIO::UDSEntry::UDS_MIME_TYPE, "application/x-nepomuk-resource" ); } // // We always want the display type, even for pimo thing linked files, in the // end showing "Invoice" or "Letter" is better than "text file" // However, mimetypes are better than generic stuff like pimo:Thing and pimo:Document // Nepomuk::Types::Class type( res.pimoThing().isValid() ? res.pimoThing().resourceType() : res.resourceType() ); if ( !isPimoThingLinkedFile || type.uri() != Nepomuk::Vocabulary::PIMO::Thing() ) { if (!type.label().isEmpty()) uds.insert( KIO::UDSEntry::UDS_DISPLAY_TYPE, type.label() ); } // // Although in KDE 4.3 the target url is sort of deprecated, we still set it. // Cannot hurt. // if ( isPimoThingLinkedFile ) uds.insert( KIO::UDSEntry::UDS_TARGET_URL, url.url() ); else uds.insert( KIO::UDSEntry::UDS_TARGET_URL, result.resourceUri().toString() ); } // // make sure we have no duplicate names // QString name = uds.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME ); if ( name.isEmpty() ) { name = uds.stringValue( KIO::UDSEntry::UDS_NAME ); } // the name is empty if the resource could not be stated if ( !name.isEmpty() ) { int cnt = 0; if ( m_nameCntHash.contains( name ) ) { cnt = ++m_nameCntHash[name]; } else { cnt = m_nameCntHash[name] = 0; } if ( cnt >= 1 ) { name = addCounterToFileName( name, cnt ); } uds.insert( KIO::UDSEntry::UDS_NAME, name ); uds.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, name ); SearchEntry* entry = new SearchEntry( result.resourceUri(), uds ); m_entries.insert( name, entry ); m_resourceNameMap.insert( result.resourceUri(), name ); kDebug() << "Stating" << result.resourceUri() << "done"; return entry; } else { // no valid name -> no valid uds kDebug() << "Stating" << result.resourceUri() << "failed"; return 0; } } diff --git a/kioslaves/search/searchfolder.h b/kioslaves/search/searchfolder.h index da952f5b..7e4b2899 100644 --- a/kioslaves/search/searchfolder.h +++ b/kioslaves/search/searchfolder.h @@ -1,146 +1,138 @@ /* Copyright (C) 2008-2009 by Sebastian Trueg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _NEPOMUK_SEARCH_FOLDER_H_ #define _NEPOMUK_SEARCH_FOLDER_H_ #include #include #include #include #include #include #include "term.h" #include "result.h" #include "query.h" #include #include #include #include namespace Nepomuk { namespace Search { class QueryServiceClient; } class SearchFolder; class SearchEntry { public: SearchEntry( const QUrl& uri, const KIO::UDSEntry& = KIO::UDSEntry() ); QUrl resource() const { return m_resource; } KIO::UDSEntry entry() const { return m_entry; } private: QUrl m_resource; KIO::UDSEntry m_entry; friend class SearchFolder; }; class SearchFolder : public QThread { Q_OBJECT public: SearchFolder(); SearchFolder( const QString& name, const Search::Query& query, KIO::SlaveBase* slave ); ~SearchFolder(); Search::Query query() const { return m_query; } QString name() const { return m_name; } QList entries() const { return m_entries.values(); } - SearchEntry* findEntry( const QString& name ) const; - SearchEntry* findEntry( const KUrl& url ) const; + SearchEntry* findEntry( const QString& name ); + SearchEntry* findEntry( const KUrl& url ); void list(); void stat( const QString& name ); private Q_SLOTS: void slotNewEntries( const QList& ); void slotEntriesRemoved( const QList& ); void slotFinishedListing(); void slotStatNextResult(); private: // reimplemented from QThread -> does handle the query // we run this in a different thread since SlaveBase does // not have an event loop but we need to deliver signals // anyway void run(); /** * Stats the result and returns the entry. */ SearchEntry* statResult( const Search::Result& result ); void wrap(); // folder properties QString m_name; Search::Query m_query; // result cache - QQueue m_results; + QQueue m_resultsQueue; QHash m_entries; QHash m_resourceNameMap; // used to make sure we have unique names // FIXME: the changed names could clash again! QHash m_nameCntHash; // true if the client listed all results and new // ones do not need to be listed but emitted through // KDirNotify bool m_initialListingFinished; // the parent slave used for listing and stating KIO::SlaveBase* m_slave; - // if set, this is the name that was requested through - // stat(). Used during initial listing when not all results - // are available yet - QString m_nameToStat; - - // true if stating of an entry has been requested (name of entry in m_nameToStat) - bool m_statEntry; - // true if listing of entries has been requested bool m_listEntries; // true if the stat loop is running bool m_statingStarted; // used to make all calls sync QEventLoop m_loop; Search::QueryServiceClient* m_client; // mutex to protect the results QMutex m_resultMutex; }; } #endif