/* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis 2000 Carsten Pfeiffer 2001-2005 Michael Brade This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdirlister.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdirlister_p.h" #include #include KDirListerCache* KDirListerCache::s_pSelf = 0; static KStaticDeleter sd_KDirListerCache; // Enable this to get printDebug() called often, to see the contents of the cache //#define DEBUG_CACHE // Make really sure it doesn't get activated in the final build #ifdef NDEBUG #undef DEBUG_CACHE #endif KDirListerCache::KDirListerCache( int maxCount ) : itemsCached( maxCount ) { kdDebug(7004) << "+KDirListerCache" << endl; itemsInUse.setAutoDelete( false ); itemsCached.setAutoDelete( true ); urlsCurrentlyListed.setAutoDelete( true ); urlsCurrentlyHeld.setAutoDelete( true ); pendingUpdates.setAutoDelete( true ); connect( kdirwatch, TQT_SIGNAL( dirty( const TQString& ) ), this, TQT_SLOT( slotFileDirty( const TQString& ) ) ); connect( kdirwatch, TQT_SIGNAL( created( const TQString& ) ), this, TQT_SLOT( slotFileCreated( const TQString& ) ) ); connect( kdirwatch, TQT_SIGNAL( deleted( const TQString& ) ), this, TQT_SLOT( slotFileDeleted( const TQString& ) ) ); } KDirListerCache::~KDirListerCache() { kdDebug(7004) << "-KDirListerCache" << endl; itemsInUse.setAutoDelete( true ); itemsInUse.clear(); itemsCached.clear(); urlsCurrentlyListed.clear(); urlsCurrentlyHeld.clear(); if ( KDirWatch::exists() ) kdirwatch->disconnect( this ); } // setting _reload to true will emit the old files and // call updateDirectory bool KDirListerCache::listDir( KDirLister *lister, const KURL& _u, bool _keep, bool _reload ) { // like this we don't have to worry about trailing slashes any further KURL _url = _u; _url.cleanPath(); // kill consecutive slashes _url.adjustPath(-1); TQString urlStr = _url.url(); if ( !lister->validURL( _url ) ) return false; #ifdef DEBUG_CACHE printDebug(); #endif kdDebug(7004) << k_funcinfo << lister << " url=" << _url << " keep=" << _keep << " reload=" << _reload << endl; if ( !_keep ) { // stop any running jobs for lister stop( lister ); // clear our internal list for lister forgetDirs( lister ); lister->d->rootFileItem = 0; } else if ( lister->d->lstDirs.find( _url ) != lister->d->lstDirs.end() ) { // stop the job listing _url for this lister stop( lister, _url ); // clear _url for lister forgetDirs( lister, _url, true ); if ( lister->d->url == _url ) lister->d->rootFileItem = 0; } lister->d->lstDirs.append( _url ); if ( lister->d->url.isEmpty() || !_keep ) // set toplevel URL only if not set yet lister->d->url = _url; DirItem *itemU = itemsInUse[urlStr]; DirItem *itemC; if ( !urlsCurrentlyListed[urlStr] ) { // if there is an update running for _url already we get into // the following case - it will just be restarted by updateDirectory(). if ( itemU ) { kdDebug(7004) << "listDir: Entry already in use: " << _url << endl; bool oldState = lister->d->complete; lister->d->complete = false; emit lister->started( _url ); if ( !lister->d->rootFileItem && lister->d->url == _url ) lister->d->rootFileItem = itemU->rootItem; lister->addNewItems( *(itemU->lstItems) ); lister->emitItems(); // _url is already in use, so there is already an entry in urlsCurrentlyHeld assert( urlsCurrentlyHeld[urlStr] ); urlsCurrentlyHeld[urlStr]->append( lister ); lister->d->complete = oldState; emit lister->completed( _url ); if ( lister->d->complete ) emit lister->completed(); if ( _reload || !itemU->complete ) updateDirectory( _url ); } else if ( !_reload && (itemC = itemsCached.take( urlStr )) ) { kdDebug(7004) << "listDir: Entry in cache: " << _url << endl; itemC->decAutoUpdate(); itemsInUse.insert( urlStr, itemC ); itemU = itemC; bool oldState = lister->d->complete; lister->d->complete = false; emit lister->started( _url ); if ( !lister->d->rootFileItem && lister->d->url == _url ) lister->d->rootFileItem = itemC->rootItem; lister->addNewItems( *(itemC->lstItems) ); lister->emitItems(); Q_ASSERT( !urlsCurrentlyHeld[urlStr] ); TQPtrList *list = new TQPtrList; list->append( lister ); urlsCurrentlyHeld.insert( urlStr, list ); lister->d->complete = oldState; emit lister->completed( _url ); if ( lister->d->complete ) emit lister->completed(); if ( !itemC->complete ) updateDirectory( _url ); } else // dir not in cache or _reload is true { kdDebug(7004) << "listDir: Entry not in cache or reloaded: " << _url << endl; TQPtrList *list = new TQPtrList; list->append( lister ); urlsCurrentlyListed.insert( urlStr, list ); itemsCached.remove( urlStr ); itemU = new DirItem( _url ); itemsInUse.insert( urlStr, itemU ); // // we have a limit of MAX_JOBS_PER_LISTER concurrently running jobs // if ( lister->numJobs() >= MAX_JOBS_PER_LISTER ) // { // lstPendingUpdates.append( _url ); // } // else // { if ( lister->d->url == _url ) lister->d->rootFileItem = 0; TDEIO::ListJob* job = TDEIO::listDir( _url, false /* no default GUI */ ); jobs.insert( job, TQValueList() ); lister->jobStarted( job ); lister->connectJob( job ); if ( lister->d->window ) job->setWindow( lister->d->window ); connect( job, TQT_SIGNAL( entries( TDEIO::Job *, const TDEIO::UDSEntryList & ) ), this, TQT_SLOT( slotEntries( TDEIO::Job *, const TDEIO::UDSEntryList & ) ) ); connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ), this, TQT_SLOT( slotResult( TDEIO::Job * ) ) ); connect( job, TQT_SIGNAL( redirection( TDEIO::Job *, const KURL & ) ), this, TQT_SLOT( slotRedirection( TDEIO::Job *, const KURL & ) ) ); emit lister->started( _url ); // } } } else { kdDebug(7004) << "listDir: Entry currently being listed: " << _url << endl; emit lister->started( _url ); urlsCurrentlyListed[urlStr]->append( lister ); TDEIO::ListJob *job = jobForUrl( urlStr ); Q_ASSERT( job ); lister->jobStarted( job ); lister->connectJob( job ); Q_ASSERT( itemU ); if ( !lister->d->rootFileItem && lister->d->url == _url ) lister->d->rootFileItem = itemU->rootItem; lister->addNewItems( *(itemU->lstItems) ); lister->emitItems(); } // automatic updating of directories if ( lister->d->autoUpdate ) itemU->incAutoUpdate(); return true; } bool KDirListerCache::validURL( const KDirLister *lister, const KURL& url ) const { if ( !url.isValid() ) { if ( lister->d->autoErrorHandling ) { TQString tmp = i18n("Malformed URL\n%1").arg( url.prettyURL() ); KMessageBox::error( lister->d->errorParent, tmp ); } return false; } if ( !KProtocolInfo::supportsListing( url ) ) { if ( lister->d->autoErrorHandling ) { // TODO: this message should be changed during next string unfreeze! TQString tmp = i18n("Malformed URL\n%1").arg( url.prettyURL() ); KMessageBox::error( lister->d->errorParent, tmp ); } return false; } return true; } void KDirListerCache::stop( KDirLister *lister ) { #ifdef DEBUG_CACHE printDebug(); #endif kdDebug(7004) << k_funcinfo << "lister: " << lister << endl; bool stopped = false; TQDictIterator< TQPtrList > it( urlsCurrentlyListed ); TQPtrList *listers; while ( (listers = it.current()) ) { if ( listers->findRef( lister ) > -1 ) { // lister is listing url TQString url = it.currentKey(); //kdDebug(7004) << k_funcinfo << " found lister in list - for " << url << endl; bool ret = listers->removeRef( lister ); Q_ASSERT( ret ); TDEIO::ListJob *job = jobForUrl( url ); if ( job ) lister->jobDone( job ); // move lister to urlsCurrentlyHeld TQPtrList *holders = urlsCurrentlyHeld[url]; if ( !holders ) { holders = new TQPtrList; urlsCurrentlyHeld.insert( url, holders ); } holders->append( lister ); emit lister->canceled( KURL( url ) ); //kdDebug(7004) << k_funcinfo << "remaining list: " << listers->count() << " listers" << endl; if ( listers->isEmpty() ) { // kill the job since it isn't used any more if ( job ) killJob( job ); urlsCurrentlyListed.remove( url ); } stopped = true; } else ++it; } if ( stopped ) { emit lister->canceled(); lister->d->complete = true; } // this is wrong if there is still an update running! //Q_ASSERT( lister->d->complete ); } void KDirListerCache::stop( KDirLister *lister, const KURL& _u ) { TQString urlStr( _u.url(-1) ); KURL _url( urlStr ); // TODO: consider to stop all the "child jobs" of _url as well kdDebug(7004) << k_funcinfo << lister << " url=" << _url << endl; TQPtrList *listers = urlsCurrentlyListed[urlStr]; if ( !listers || !listers->removeRef( lister ) ) return; // move lister to urlsCurrentlyHeld TQPtrList *holders = urlsCurrentlyHeld[urlStr]; if ( !holders ) { holders = new TQPtrList; urlsCurrentlyHeld.insert( urlStr, holders ); } holders->append( lister ); TDEIO::ListJob *job = jobForUrl( urlStr ); if ( job ) lister->jobDone( job ); emit lister->canceled( _url ); if ( listers->isEmpty() ) { // kill the job since it isn't used any more if ( job ) killJob( job ); urlsCurrentlyListed.remove( urlStr ); } if ( lister->numJobs() == 0 ) { lister->d->complete = true; // we killed the last job for lister emit lister->canceled(); } } void KDirListerCache::setAutoUpdate( KDirLister *lister, bool enable ) { // IMPORTANT: this method does not check for the current autoUpdate state! for ( KURL::List::Iterator it = lister->d->lstDirs.begin(); it != lister->d->lstDirs.end(); ++it ) { if ( enable ) itemsInUse[(*it).url()]->incAutoUpdate(); else itemsInUse[(*it).url()]->decAutoUpdate(); } } void KDirListerCache::forgetDirs( KDirLister *lister ) { kdDebug(7004) << k_funcinfo << lister << endl; emit lister->clear(); // forgetDirs() will modify lstDirs, make a copy first KURL::List lstDirsCopy = lister->d->lstDirs; for ( KURL::List::Iterator it = lstDirsCopy.begin(); it != lstDirsCopy.end(); ++it ) { forgetDirs( lister, *it, false ); } } void KDirListerCache::forgetDirs( KDirLister *lister, const KURL& _url, bool notify ) { kdDebug(7004) << k_funcinfo << lister << " _url: " << _url << endl; KURL url( _url ); url.adjustPath( -1 ); TQString urlStr = url.url(); TQPtrList *holders = urlsCurrentlyHeld[urlStr]; //Q_ASSERT( holders ); if ( holders ) { holders->removeRef( lister ); } // remove the dir from lister->d->lstDirs so that it doesn't contain things // that itemsInUse doesn't. When emitting the canceled signals lstDirs must // not contain anything that itemsInUse does not contain. (otherwise it // might crash in findByName()). lister->d->lstDirs.remove( lister->d->lstDirs.find( url ) ); DirItem *item = itemsInUse[urlStr]; if ( holders && holders->isEmpty() ) { urlsCurrentlyHeld.remove( urlStr ); // this deletes the (empty) holders list if ( !urlsCurrentlyListed[urlStr] ) { // item not in use anymore -> move into cache if complete itemsInUse.remove( urlStr ); // this job is a running update TDEIO::ListJob *job = jobForUrl( urlStr ); if ( job ) { lister->jobDone( job ); killJob( job ); kdDebug(7004) << k_funcinfo << "Killing update job for " << urlStr << endl; emit lister->canceled( url ); if ( lister->numJobs() == 0 ) { lister->d->complete = true; emit lister->canceled(); } } if ( notify ) emit lister->clear( url ); if ( item && item->complete ) { kdDebug(7004) << k_funcinfo << lister << " item moved into cache: " << url << endl; itemsCached.insert( urlStr, item ); // TODO: may return false!! // Should we forget the dir for good, or keep a watch on it? // Generally keep a watch, except when it would prevent // unmounting a removable device (#37780) const bool isLocal = item->url.isLocalFile(); const bool isManuallyMounted = isLocal && TDEIO::manually_mounted( item->url.path() ); bool containsManuallyMounted = false; if ( !isManuallyMounted && item->lstItems && isLocal ) { // Look for a manually-mounted directory inside // If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM // I hope this isn't too slow (manually_mounted caches the last device so most // of the time this is just a stat per subdir) KFileItemListIterator kit( *item->lstItems ); for ( ; kit.current() && !containsManuallyMounted; ++kit ) if ( (*kit)->isDir() && TDEIO::manually_mounted( (*kit)->url().path() ) ) containsManuallyMounted = true; } if ( isManuallyMounted || containsManuallyMounted ) { kdDebug(7004) << "Not adding a watch on " << item->url << " because it " << ( isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir" ) << endl; item->complete = false; // set to "dirty" } else item->incAutoUpdate(); // keep watch } else { delete item; item = 0; } } } if ( item && lister->d->autoUpdate ) item->decAutoUpdate(); } void KDirListerCache::updateDirectory( const KURL& _dir ) { kdDebug(7004) << k_funcinfo << _dir << endl; TQString urlStr = _dir.url(-1); if ( !checkUpdate( urlStr ) ) return; // A job can be running to // - only list a new directory: the listers are in urlsCurrentlyListed // - only update a directory: the listers are in urlsCurrentlyHeld // - update a currently running listing: the listers are in urlsCurrentlyListed // and urlsCurrentlyHeld TQPtrList *listers = urlsCurrentlyListed[urlStr]; TQPtrList *holders = urlsCurrentlyHeld[urlStr]; // restart the job for _dir if it is running already bool killed = false; TQWidget *window = 0; TDEIO::ListJob *job = jobForUrl( urlStr ); if ( job ) { window = job->window(); killJob( job ); killed = true; if ( listers ) for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) kdl->jobDone( job ); if ( holders ) for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() ) kdl->jobDone( job ); } kdDebug(7004) << k_funcinfo << "Killed = " << killed << endl; // we don't need to emit canceled signals since we only replaced the job, // the listing is continuing. Q_ASSERT( !listers || (listers && killed) ); job = TDEIO::listDir( _dir, false /* no default GUI */ ); jobs.insert( job, TQValueList() ); connect( job, TQT_SIGNAL(entries( TDEIO::Job *, const TDEIO::UDSEntryList & )), this, TQT_SLOT(slotUpdateEntries( TDEIO::Job *, const TDEIO::UDSEntryList & )) ); connect( job, TQT_SIGNAL(result( TDEIO::Job * )), this, TQT_SLOT(slotUpdateResult( TDEIO::Job * )) ); kdDebug(7004) << k_funcinfo << "update started in " << _dir << endl; if ( listers ) for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) kdl->jobStarted( job ); if ( holders ) { if ( !killed ) { bool first = true; for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() ) { kdl->jobStarted( job ); if ( first && kdl->d->window ) { first = false; job->setWindow( kdl->d->window ); } emit kdl->started( _dir ); } } else { job->setWindow( window ); for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() ) kdl->jobStarted( job ); } } } bool KDirListerCache::checkUpdate( const TQString& _dir ) { if ( !itemsInUse[_dir] ) { DirItem *item = itemsCached[_dir]; if ( item && item->complete ) { item->complete = false; item->decAutoUpdate(); // Hmm, this debug output might include login/password from the _dir URL. //kdDebug(7004) << k_funcinfo << "directory " << _dir << " not in use, marked dirty." << endl; } //else //kdDebug(7004) << k_funcinfo << "aborted, directory " << _dir << " not in cache." << endl; return false; } else return true; } KFileItemList *KDirListerCache::itemsForDir( const KURL &_dir ) const { TQString urlStr = _dir.url(-1); DirItem *item = itemsInUse[ urlStr ]; if ( !item ) item = itemsCached[ urlStr ]; return item ? item->lstItems : 0; } KFileItem *KDirListerCache::findByName( const KDirLister *lister, const TQString& _name ) const { Q_ASSERT( lister ); for ( KURL::List::Iterator it = lister->d->lstDirs.begin(); it != lister->d->lstDirs.end(); ++it ) { KFileItemListIterator kit( *itemsInUse[(*it).url()]->lstItems ); for ( ; kit.current(); ++kit ) if ( (*kit)->name() == _name ) return (*kit); } return 0L; } KFileItem *KDirListerCache::findByURL( const KDirLister *lister, const KURL& _u ) const { KURL _url = _u; _url.adjustPath(-1); KURL parentDir( _url ); parentDir.setPath( parentDir.directory() ); // If lister is set, check that it contains this dir if ( lister && !lister->d->lstDirs.contains( parentDir ) ) return 0L; KFileItemList *itemList = itemsForDir( parentDir ); if ( itemList ) { KFileItemListIterator kit( *itemList ); for ( ; kit.current(); ++kit ) if ( (*kit)->url() == _url ) return (*kit); } return 0L; } void KDirListerCache::FilesAdded( const KURL &dir ) { kdDebug(7004) << k_funcinfo << dir << endl; updateDirectory( dir ); } void KDirListerCache::FilesRemoved( const KURL::List &fileList ) { kdDebug(7004) << k_funcinfo << endl; KURL::List::ConstIterator it = fileList.begin(); for ( ; it != fileList.end() ; ++it ) { // emit the deleteItem signal if this file was shown in any view KFileItem *fileitem = 0L; KURL parentDir( *it ); parentDir.setPath( parentDir.directory() ); KFileItemList *lstItems = itemsForDir( parentDir ); if ( lstItems ) { KFileItem *fit = lstItems->first(); for ( ; fit; fit = lstItems->next() ) if ( fit->url() == *it ) { fileitem = fit; lstItems->take(); // remove fileitem from list break; } } // Tell the views about it before deleting the KFileItems. They might need the subdirs' // file items (see the dirtree). if ( fileitem ) { TQPtrList *listers = urlsCurrentlyHeld[parentDir.url()]; if ( listers ) for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) kdl->emitDeleteItem( fileitem ); } // If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case. if ( !fileitem || fileitem->isDir() ) { // in case of a dir, check if we have any known children, there's much to do in that case // (stopping jobs, removing dirs from cache etc.) deleteDir( *it ); } // now remove the item itself delete fileitem; } } void KDirListerCache::FilesChanged( const KURL::List &fileList ) { KURL::List dirsToUpdate; kdDebug(7004) << k_funcinfo << "only half implemented" << endl; KURL::List::ConstIterator it = fileList.begin(); for ( ; it != fileList.end() ; ++it ) { if ( ( *it ).isLocalFile() ) { kdDebug(7004) << "KDirListerCache::FilesChanged " << *it << endl; KFileItem *fileitem = findByURL( 0, *it ); if ( fileitem ) { // we need to refresh the item, because e.g. the permissions can have changed. aboutToRefreshItem( fileitem ); fileitem->refresh(); emitRefreshItem( fileitem ); } else kdDebug(7004) << "item not found" << endl; } else { // For remote files, refresh() won't be able to figure out the new information. // Let's update the dir. KURL dir( *it ); dir.setPath( dir.directory( true ) ); if ( dirsToUpdate.find( dir ) == dirsToUpdate.end() ) dirsToUpdate.prepend( dir ); } } KURL::List::ConstIterator itdir = dirsToUpdate.begin(); for ( ; itdir != dirsToUpdate.end() ; ++itdir ) updateDirectory( *itdir ); // ## TODO problems with current jobs listing/updating that dir // ( see kde-2.2.2's kdirlister ) } void KDirListerCache::FileRenamed( const KURL &src, const KURL &dst ) { kdDebug(7004) << k_funcinfo << src.prettyURL() << " -> " << dst.prettyURL() << endl; #ifdef DEBUG_CACHE printDebug(); #endif // Somehow this should only be called if src is a dir. But how could we know if it is? // (Note that looking into itemsInUse isn't good enough. One could rename a subdir in a view.) renameDir( src, dst ); // Now update the KFileItem representing that file or dir (not exclusive with the above!) KURL oldurl( src ); oldurl.adjustPath( -1 ); KFileItem *fileitem = findByURL( 0, oldurl ); if ( fileitem ) { if ( !fileitem->isLocalFile() && !fileitem->localPath().isEmpty() ) // it uses UDS_LOCAL_PATH? ouch, needs an update then FilesChanged( src ); else { aboutToRefreshItem( fileitem ); fileitem->setURL( dst ); fileitem->refreshMimeType(); emitRefreshItem( fileitem ); } } #ifdef DEBUG_CACHE printDebug(); #endif } void KDirListerCache::aboutToRefreshItem( KFileItem *fileitem ) { // Look whether this item was shown in any view, i.e. held by any dirlister KURL parentDir( fileitem->url() ); parentDir.setPath( parentDir.directory() ); TQString parentDirURL = parentDir.url(); TQPtrList *listers = urlsCurrentlyHeld[parentDirURL]; if ( listers ) for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) kdl->aboutToRefreshItem( fileitem ); // Also look in urlsCurrentlyListed, in case the user manages to rename during a listing listers = urlsCurrentlyListed[parentDirURL]; if ( listers ) for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) kdl->aboutToRefreshItem( fileitem ); } void KDirListerCache::emitRefreshItem( KFileItem *fileitem ) { // Look whether this item was shown in any view, i.e. held by any dirlister KURL parentDir( fileitem->url() ); parentDir.setPath( parentDir.directory() ); TQString parentDirURL = parentDir.url(); TQPtrList *listers = urlsCurrentlyHeld[parentDirURL]; if ( listers ) for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) { kdl->addRefreshItem( fileitem ); kdl->emitItems(); } // Also look in urlsCurrentlyListed, in case the user manages to rename during a listing listers = urlsCurrentlyListed[parentDirURL]; if ( listers ) for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) { kdl->addRefreshItem( fileitem ); kdl->emitItems(); } } KDirListerCache* KDirListerCache::self() { if ( !s_pSelf ) s_pSelf = sd_KDirListerCache.setObject( s_pSelf, new KDirListerCache ); return s_pSelf; } bool KDirListerCache::exists() { return s_pSelf != 0; } // private slots // _file can also be a directory being currently held! void KDirListerCache::slotFileDirty( const TQString& _file ) { kdDebug(7004) << k_funcinfo << _file << endl; if ( !pendingUpdates[_file] ) { KURL dir; dir.setPath( _file ); if ( checkUpdate( dir.url(-1) ) ) updateDirectory( dir ); // the parent directory of _file dir.setPath( dir.directory() ); if ( checkUpdate( dir.url() ) ) { // Nice hack to save memory: use the qt object name to store the filename TQTimer *timer = new TQTimer( this, _file.utf8() ); connect( timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotFileDirtyDelayed()) ); pendingUpdates.insert( _file, timer ); timer->start( 500, true ); } } } // delayed updating of files, FAM is flooding us with events void KDirListerCache::slotFileDirtyDelayed() { TQString file = TQString::fromUtf8( TQT_TQOBJECT_CONST(sender())->name() ); kdDebug(7004) << k_funcinfo << file << endl; // TODO: do it better: don't always create/delete the TQTimer but reuse it. // Delete the timer after the parent directory is removed from the cache. pendingUpdates.remove( file ); KURL u; u.setPath( file ); KFileItem *item = findByURL( 0, u ); // search all items if ( item ) { // we need to refresh the item, because e.g. the permissions can have changed. aboutToRefreshItem( item ); item->refresh(); emitRefreshItem( item ); } } void KDirListerCache::slotFileCreated( const TQString& _file ) { kdDebug(7004) << k_funcinfo << _file << endl; // XXX: how to avoid a complete rescan here? KURL u; u.setPath( _file ); u.setPath( u.directory() ); FilesAdded( u ); } void KDirListerCache::slotFileDeleted( const TQString& _file ) { kdDebug(7004) << k_funcinfo << _file << endl; KURL u; u.setPath( _file ); FilesRemoved( u ); } void KDirListerCache::slotEntries( TDEIO::Job *job, const TDEIO::UDSEntryList &entries ) { KURL url = joburl( static_cast(job) ); url.adjustPath(-1); TQString urlStr = url.url(); kdDebug(7004) << k_funcinfo << "new entries for " << url << endl; DirItem *dir = itemsInUse[urlStr]; Q_ASSERT( dir ); TQPtrList *listers = urlsCurrentlyListed[urlStr]; Q_ASSERT( listers ); Q_ASSERT( !listers->isEmpty() ); // check if anyone wants the mimetypes immediately bool delayedMimeTypes = true; for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) delayedMimeTypes = delayedMimeTypes && kdl->d->delayedMimeTypes; // avoid creating these QStrings again and again static const TQString& dot = TDEGlobal::staticQString("."); static const TQString& dotdot = TDEGlobal::staticQString(".."); TDEIO::UDSEntryListConstIterator it = entries.begin(); TDEIO::UDSEntryListConstIterator end = entries.end(); for ( ; it != end; ++it ) { TQString name; // find out about the name TDEIO::UDSEntry::ConstIterator entit = (*it).begin(); for( ; entit != (*it).end(); ++entit ) if ( (*entit).m_uds == TDEIO::UDS_NAME ) { name = (*entit).m_str; break; } Q_ASSERT( !name.isEmpty() ); if ( name.isEmpty() ) continue; if ( name == dot ) { Q_ASSERT( !dir->rootItem ); dir->rootItem = new KFileItem( *it, url, delayedMimeTypes, true ); for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) if ( !kdl->d->rootFileItem && kdl->d->url == url ) kdl->d->rootFileItem = dir->rootItem; } else if ( name != dotdot ) { KFileItem* item = new KFileItem( *it, url, delayedMimeTypes, true ); Q_ASSERT( item ); //kdDebug(7004)<< "Adding item: " << item->url() << endl; dir->lstItems->append( item ); for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) kdl->addNewItem( item ); } } for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) kdl->emitItems(); } void KDirListerCache::slotResult( TDEIO::Job *j ) { Q_ASSERT( j ); TDEIO::ListJob *job = static_cast( j ); jobs.remove( job ); KURL jobUrl = joburl( job ); jobUrl.adjustPath(-1); // need remove trailing slashes again, in case of redirections TQString jobUrlStr = jobUrl.url(); kdDebug(7004) << k_funcinfo << "finished listing " << jobUrl << endl; #ifdef DEBUG_CACHE printDebug(); #endif TQPtrList *listers = urlsCurrentlyListed.take( jobUrlStr ); Q_ASSERT( listers ); // move the directory to the held directories, do it before emitting // the signals to make sure it exists in KDirListerCache in case someone // calls listDir during the signal emission Q_ASSERT( !urlsCurrentlyHeld[jobUrlStr] ); urlsCurrentlyHeld.insert( jobUrlStr, listers ); KDirLister *kdl; if ( job->error() ) { for ( kdl = listers->first(); kdl; kdl = listers->next() ) { kdl->jobDone( job ); kdl->handleError( job ); emit kdl->canceled( jobUrl ); if ( kdl->numJobs() == 0 ) { kdl->d->complete = true; emit kdl->canceled(); } } } else { DirItem *dir = itemsInUse[jobUrlStr]; Q_ASSERT( dir ); dir->complete = true; for ( kdl = listers->first(); kdl; kdl = listers->next() ) { kdl->jobDone( job ); emit kdl->completed( jobUrl ); if ( kdl->numJobs() == 0 ) { kdl->d->complete = true; emit kdl->completed(); } } } // TODO: hmm, if there was an error and job is a parent of one or more // of the pending urls we should cancel it/them as well processPendingUpdates(); #ifdef DEBUG_CACHE printDebug(); #endif } void KDirListerCache::slotRedirection( TDEIO::Job *j, const KURL& url ) { Q_ASSERT( j ); TDEIO::ListJob *job = static_cast( j ); KURL oldUrl = job->url(); // here we really need the old url! KURL newUrl = url; // strip trailing slashes oldUrl.adjustPath(-1); newUrl.adjustPath(-1); if ( oldUrl == newUrl ) { kdDebug(7004) << k_funcinfo << "New redirection url same as old, giving up." << endl; return; } kdDebug(7004) << k_funcinfo << oldUrl.prettyURL() << " -> " << newUrl.prettyURL() << endl; #ifdef DEBUG_CACHE printDebug(); #endif // I don't think there can be dirItems that are childs of oldUrl. // Am I wrong here? And even if so, we don't need to delete them, right? // DF: redirection happens before listDir emits any item. Makes little sense otherwise. // oldUrl cannot be in itemsCached because only completed items are moved there DirItem *dir = itemsInUse.take( oldUrl.url() ); Q_ASSERT( dir ); TQPtrList *listers = urlsCurrentlyListed.take( oldUrl.url() ); Q_ASSERT( listers ); Q_ASSERT( !listers->isEmpty() ); for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) { // TODO: put in own method? if ( kdl->d->url.equals( oldUrl, true ) ) { kdl->d->rootFileItem = 0; kdl->d->url = newUrl; } *kdl->d->lstDirs.find( oldUrl ) = newUrl; if ( kdl->d->lstDirs.count() == 1 ) { emit kdl->clear(); emit kdl->redirection( newUrl ); emit kdl->redirection( oldUrl, newUrl ); } else { emit kdl->clear( oldUrl ); emit kdl->redirection( oldUrl, newUrl ); } } // when a lister was stopped before the job emits the redirection signal, the old url will // also be in urlsCurrentlyHeld TQPtrList *holders = urlsCurrentlyHeld.take( oldUrl.url() ); if ( holders ) { Q_ASSERT( !holders->isEmpty() ); for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() ) { kdl->jobStarted( job ); // do it like when starting a new list-job that will redirect later emit kdl->started( oldUrl ); // TODO: maybe don't emit started if there's an update running for newUrl already? if ( kdl->d->url.equals( oldUrl, true ) ) { kdl->d->rootFileItem = 0; kdl->d->url = newUrl; } *kdl->d->lstDirs.find( oldUrl ) = newUrl; if ( kdl->d->lstDirs.count() == 1 ) { emit kdl->clear(); emit kdl->redirection( newUrl ); emit kdl->redirection( oldUrl, newUrl ); } else { emit kdl->clear( oldUrl ); emit kdl->redirection( oldUrl, newUrl ); } } } DirItem *newDir = itemsInUse[newUrl.url()]; if ( newDir ) { kdDebug(7004) << "slotRedirection: " << newUrl.url() << " already in use" << endl; // only in this case there can newUrl already be in urlsCurrentlyListed or urlsCurrentlyHeld delete dir; // get the job if one's running for newUrl already (can be a list-job or an update-job), but // do not return this 'job', which would happen because of the use of redirectionURL() TDEIO::ListJob *oldJob = jobForUrl( newUrl.url(), job ); // listers of newUrl with oldJob: forget about the oldJob and use the already running one // which will be converted to an updateJob TQPtrList *curListers = urlsCurrentlyListed[newUrl.url()]; if ( curListers ) { kdDebug(7004) << "slotRedirection: and it is currently listed" << endl; Q_ASSERT( oldJob ); // ?! for ( KDirLister *kdl = curListers->first(); kdl; kdl = curListers->next() ) // listers of newUrl { kdl->jobDone( oldJob ); kdl->jobStarted( job ); kdl->connectJob( job ); } // append listers of oldUrl with newJob to listers of newUrl with oldJob for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) curListers->append( kdl ); } else urlsCurrentlyListed.insert( newUrl.url(), listers ); if ( oldJob ) // kill the old job, be it a list-job or an update-job killJob( oldJob ); // holders of newUrl: use the already running job which will be converted to an updateJob TQPtrList *curHolders = urlsCurrentlyHeld[newUrl.url()]; if ( curHolders ) { kdDebug(7004) << "slotRedirection: and it is currently held." << endl; for ( KDirLister *kdl = curHolders->first(); kdl; kdl = curHolders->next() ) // holders of newUrl { kdl->jobStarted( job ); emit kdl->started( newUrl ); } // append holders of oldUrl to holders of newUrl if ( holders ) for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() ) curHolders->append( kdl ); } else if ( holders ) urlsCurrentlyHeld.insert( newUrl.url(), holders ); // emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed // TODO: make this a separate method? for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) { if ( !kdl->d->rootFileItem && kdl->d->url == newUrl ) kdl->d->rootFileItem = newDir->rootItem; kdl->addNewItems( *(newDir->lstItems) ); kdl->emitItems(); } if ( holders ) { for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() ) { if ( !kdl->d->rootFileItem && kdl->d->url == newUrl ) kdl->d->rootFileItem = newDir->rootItem; kdl->addNewItems( *(newDir->lstItems) ); kdl->emitItems(); } } } else if ( (newDir = itemsCached.take( newUrl.url() )) ) { kdDebug(7004) << "slotRedirection: " << newUrl.url() << " is unused, but already in the cache." << endl; delete dir; itemsInUse.insert( newUrl.url(), newDir ); urlsCurrentlyListed.insert( newUrl.url(), listers ); if ( holders ) urlsCurrentlyHeld.insert( newUrl.url(), holders ); // emit old items: listers, holders for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) { if ( !kdl->d->rootFileItem && kdl->d->url == newUrl ) kdl->d->rootFileItem = newDir->rootItem; kdl->addNewItems( *(newDir->lstItems) ); kdl->emitItems(); } if ( holders ) { for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() ) { if ( !kdl->d->rootFileItem && kdl->d->url == newUrl ) kdl->d->rootFileItem = newDir->rootItem; kdl->addNewItems( *(newDir->lstItems) ); kdl->emitItems(); } } } else { kdDebug(7004) << "slotRedirection: " << newUrl.url() << " has not been listed yet." << endl; delete dir->rootItem; dir->rootItem = 0; dir->lstItems->clear(); dir->redirect( newUrl ); itemsInUse.insert( newUrl.url(), dir ); urlsCurrentlyListed.insert( newUrl.url(), listers ); if ( holders ) urlsCurrentlyHeld.insert( newUrl.url(), holders ); else { #ifdef DEBUG_CACHE printDebug(); #endif return; // only in this case the job doesn't need to be converted, } } // make the job an update job job->disconnect( this ); connect( job, TQT_SIGNAL(entries( TDEIO::Job *, const TDEIO::UDSEntryList & )), this, TQT_SLOT(slotUpdateEntries( TDEIO::Job *, const TDEIO::UDSEntryList & )) ); connect( job, TQT_SIGNAL(result( TDEIO::Job * )), this, TQT_SLOT(slotUpdateResult( TDEIO::Job * )) ); // FIXME: autoUpdate-Counts!! #ifdef DEBUG_CACHE printDebug(); #endif } void KDirListerCache::renameDir( const KURL &oldUrl, const KURL &newUrl ) { kdDebug(7004) << k_funcinfo << oldUrl.prettyURL() << " -> " << newUrl.prettyURL() << endl; TQString oldUrlStr = oldUrl.url(-1); TQString newUrlStr = newUrl.url(-1); // Not enough. Also need to look at any child dir, even sub-sub-sub-dir. //DirItem *dir = itemsInUse.take( oldUrlStr ); //emitRedirections( oldUrl, url ); // Look at all dirs being listed/shown TQDictIterator itu( itemsInUse ); bool goNext; while ( itu.current() ) { goNext = true; DirItem *dir = itu.current(); KURL oldDirUrl ( itu.currentKey() ); //kdDebug(7004) << "itemInUse: " << oldDirUrl.prettyURL() << endl; // Check if this dir is oldUrl, or a subfolder of it if ( oldUrl.isParentOf( oldDirUrl ) ) { // TODO should use KURL::cleanpath like isParentOf does TQString relPath = oldDirUrl.path().mid( oldUrl.path().length() ); KURL newDirUrl( newUrl ); // take new base if ( !relPath.isEmpty() ) newDirUrl.addPath( relPath ); // add unchanged relative path //kdDebug(7004) << "KDirListerCache::renameDir new url=" << newDirUrl.prettyURL() << endl; // Update URL in dir item and in itemsInUse dir->redirect( newDirUrl ); itemsInUse.remove( itu.currentKey() ); // implies ++itu itemsInUse.insert( newDirUrl.url(-1), dir ); goNext = false; // because of the implied ++itu above if ( dir->lstItems ) { // Rename all items under that dir KFileItemListIterator kit( *dir->lstItems ); for ( ; kit.current(); ++kit ) { KURL oldItemUrl = (*kit)->url(); TQString oldItemUrlStr( oldItemUrl.url(-1) ); KURL newItemUrl( oldItemUrl ); newItemUrl.setPath( newDirUrl.path() ); newItemUrl.addPath( oldItemUrl.fileName() ); kdDebug(7004) << "KDirListerCache::renameDir renaming " << oldItemUrlStr << " to " << newItemUrl.url() << endl; (*kit)->setURL( newItemUrl ); } } emitRedirections( oldDirUrl, newDirUrl ); } if ( goNext ) ++itu; } // Is oldUrl a directory in the cache? // Remove any child of oldUrl from the cache - even if the renamed dir itself isn't in it! removeDirFromCache( oldUrl ); // TODO rename, instead. } void KDirListerCache::emitRedirections( const KURL &oldUrl, const KURL &url ) { kdDebug(7004) << k_funcinfo << oldUrl.prettyURL() << " -> " << url.prettyURL() << endl; TQString oldUrlStr = oldUrl.url(-1); TQString urlStr = url.url(-1); TDEIO::ListJob *job = jobForUrl( oldUrlStr ); if ( job ) killJob( job ); // Check if we were listing this dir. Need to abort and restart with new name in that case. TQPtrList *listers = urlsCurrentlyListed.take( oldUrlStr ); if ( listers ) { // Tell the world that the job listing the old url is dead. for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) { if ( job ) kdl->jobDone( job ); emit kdl->canceled( oldUrl ); } urlsCurrentlyListed.insert( urlStr, listers ); } // Check if we are currently displaying this directory (odds opposite wrt above) // Update urlsCurrentlyHeld dict with new URL TQPtrList *holders = urlsCurrentlyHeld.take( oldUrlStr ); if ( holders ) { if ( job ) for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() ) kdl->jobDone( job ); urlsCurrentlyHeld.insert( urlStr, holders ); } if ( listers ) { updateDirectory( url ); // Tell the world about the new url for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) emit kdl->started( url ); } if ( holders ) { // And notify the dirlisters of the redirection for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() ) { *kdl->d->lstDirs.find( oldUrl ) = url; if ( kdl->d->lstDirs.count() == 1 ) emit kdl->redirection( url ); emit kdl->redirection( oldUrl, url ); } } } void KDirListerCache::removeDirFromCache( const KURL& dir ) { kdDebug(7004) << "KDirListerCache::removeDirFromCache " << dir.prettyURL() << endl; TQCacheIterator itc( itemsCached ); while ( itc.current() ) { if ( dir.isParentOf( KURL( itc.currentKey() ) ) ) itemsCached.remove( itc.currentKey() ); else ++itc; } } void KDirListerCache::slotUpdateEntries( TDEIO::Job* job, const TDEIO::UDSEntryList& list ) { jobs[static_cast(job)] += list; } void KDirListerCache::slotUpdateResult( TDEIO::Job * j ) { Q_ASSERT( j ); TDEIO::ListJob *job = static_cast( j ); KURL jobUrl = joburl( job ); jobUrl.adjustPath(-1); // need remove trailing slashes again, in case of redirections TQString jobUrlStr = jobUrl.url(); kdDebug(7004) << k_funcinfo << "finished update " << jobUrl << endl; KDirLister *kdl; TQPtrList *listers = urlsCurrentlyHeld[jobUrlStr]; TQPtrList *tmpLst = urlsCurrentlyListed.take( jobUrlStr ); if ( tmpLst ) { if ( listers ) for ( kdl = tmpLst->first(); kdl; kdl = tmpLst->next() ) { Q_ASSERT( listers->containsRef( kdl ) == 0 ); listers->append( kdl ); } else { listers = tmpLst; urlsCurrentlyHeld.insert( jobUrlStr, listers ); } } // once we are updating dirs that are only in the cache this will fail! Q_ASSERT( listers ); if ( job->error() ) { for ( kdl = listers->first(); kdl; kdl = listers->next() ) { kdl->jobDone( job ); //don't bother the user //kdl->handleError( job ); emit kdl->canceled( jobUrl ); if ( kdl->numJobs() == 0 ) { kdl->d->complete = true; emit kdl->canceled(); } } jobs.remove( job ); // TODO: if job is a parent of one or more // of the pending urls we should cancel them processPendingUpdates(); return; } DirItem *dir = itemsInUse[jobUrlStr]; dir->complete = true; // check if anyone wants the mimetypes immediately bool delayedMimeTypes = true; for ( kdl = listers->first(); kdl; kdl = listers->next() ) delayedMimeTypes = delayedMimeTypes && kdl->d->delayedMimeTypes; // should be enough to get reasonable speed in most cases TQDict fileItems( 9973 ); KFileItemListIterator kit ( *(dir->lstItems) ); // Unmark all items in url for ( ; kit.current(); ++kit ) { (*kit)->unmark(); fileItems.insert( (*kit)->url().url(), *kit ); } static const TQString& dot = TDEGlobal::staticQString("."); static const TQString& dotdot = TDEGlobal::staticQString(".."); KFileItem *item = 0, *tmp; TQValueList buf = jobs[job]; TQValueListIterator it = buf.begin(); for ( ; it != buf.end(); ++it ) { // Form the complete url if ( !item ) item = new KFileItem( *it, jobUrl, delayedMimeTypes, true ); else item->setUDSEntry( *it, jobUrl, delayedMimeTypes, true ); // Find out about the name TQString name = item->name(); Q_ASSERT( !name.isEmpty() ); // we duplicate the check for dotdot here, to avoid iterating over // all items again and checking in matchesFilter() that way. if ( name.isEmpty() || name == dotdot ) continue; if ( name == dot ) { // if the update was started before finishing the original listing // there is no root item yet if ( !dir->rootItem ) { dir->rootItem = item; item = 0; for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) if ( !kdl->d->rootFileItem && kdl->d->url == jobUrl ) kdl->d->rootFileItem = dir->rootItem; } continue; } // Find this item if ( (tmp = fileItems[item->url().url()]) ) { tmp->mark(); // check if something changed for this file if ( !tmp->cmp( *item ) ) { for ( kdl = listers->first(); kdl; kdl = listers->next() ) kdl->aboutToRefreshItem( tmp ); //kdDebug(7004) << "slotUpdateResult: file changed: " << tmp->name() << endl; tmp->assign( *item ); for ( kdl = listers->first(); kdl; kdl = listers->next() ) kdl->addRefreshItem( tmp ); } } else // this is a new file { //kdDebug(7004) << "slotUpdateResult: new file: " << name << endl; item->mark(); dir->lstItems->append( item ); for ( kdl = listers->first(); kdl; kdl = listers->next() ) kdl->addNewItem( item ); // item used, we need a new one for the next iteration item = 0; } } if ( item ) delete item; jobs.remove( job ); deleteUnmarkedItems( listers, dir->lstItems ); for ( kdl = listers->first(); kdl; kdl = listers->next() ) { kdl->emitItems(); kdl->jobDone( job ); emit kdl->completed( jobUrl ); if ( kdl->numJobs() == 0 ) { kdl->d->complete = true; emit kdl->completed(); } } // TODO: hmm, if there was an error and job is a parent of one or more // of the pending urls we should cancel it/them as well processPendingUpdates(); } // private TDEIO::ListJob *KDirListerCache::jobForUrl( const TQString& url, TDEIO::ListJob *not_job ) { TDEIO::ListJob *job; TQMap< TDEIO::ListJob *, TQValueList >::Iterator it = jobs.begin(); while ( it != jobs.end() ) { job = it.key(); if ( joburl( job ).url(-1) == url && job != not_job ) return job; ++it; } return 0; } const KURL& KDirListerCache::joburl( TDEIO::ListJob *job ) { if ( job->redirectionURL().isValid() ) return job->redirectionURL(); else return job->url(); } void KDirListerCache::killJob( TDEIO::ListJob *job ) { jobs.remove( job ); job->disconnect( this ); job->kill(); } void KDirListerCache::deleteUnmarkedItems( TQPtrList *listers, KFileItemList *lstItems ) { // Find all unmarked items and delete them KFileItem* item; lstItems->first(); while ( (item = lstItems->current()) ) if ( !item->isMarked() ) { //kdDebug() << k_funcinfo << item->name() << endl; for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() ) kdl->emitDeleteItem( item ); if ( item->isDir() ) deleteDir( item->url() ); // finally actually delete the item lstItems->take(); delete item; } else lstItems->next(); } void KDirListerCache::deleteDir( const KURL& dirUrl ) { //kdDebug() << k_funcinfo << dirUrl.prettyURL() << endl; // unregister and remove the childs of the deleted item. // Idea: tell all the KDirListers that they should forget the dir // and then remove it from the cache. TQDictIterator itu( itemsInUse ); while ( itu.current() ) { KURL deletedUrl( itu.currentKey() ); if ( dirUrl.isParentOf( deletedUrl ) ) { // stop all jobs for deletedUrl TQPtrList *kdls = urlsCurrentlyListed[deletedUrl.url()]; if ( kdls ) // yeah, I lack good names { // we need a copy because stop modifies the list kdls = new TQPtrList( *kdls ); for ( KDirLister *kdl = kdls->first(); kdl; kdl = kdls->next() ) stop( kdl, deletedUrl ); delete kdls; } // tell listers holding deletedUrl to forget about it // this will stop running updates for deletedUrl as well kdls = urlsCurrentlyHeld[deletedUrl.url()]; if ( kdls ) { // we need a copy because forgetDirs modifies the list kdls = new TQPtrList( *kdls ); for ( KDirLister *kdl = kdls->first(); kdl; kdl = kdls->next() ) { // lister's root is the deleted item if ( kdl->d->url == deletedUrl ) { // tell the view first. It might need the subdirs' items (which forgetDirs will delete) if ( kdl->d->rootFileItem ) emit kdl->deleteItem( kdl->d->rootFileItem ); forgetDirs( kdl ); kdl->d->rootFileItem = 0; } else { bool treeview = kdl->d->lstDirs.count() > 1; if ( !treeview ) emit kdl->clear(); forgetDirs( kdl, deletedUrl, treeview ); } } delete kdls; } // delete the entry for deletedUrl - should not be needed, it's in // items cached now DirItem *dir = itemsInUse.take( deletedUrl.url() ); Q_ASSERT( !dir ); if ( !dir ) // take didn't find it - move on ++itu; } else ++itu; } // remove the children from the cache removeDirFromCache( dirUrl ); } void KDirListerCache::processPendingUpdates() { // TODO } #ifndef NDEBUG void KDirListerCache::printDebug() { kdDebug(7004) << "Items in use: " << endl; TQDictIterator itu( itemsInUse ); for ( ; itu.current() ; ++itu ) { kdDebug(7004) << " " << itu.currentKey() << " URL: " << itu.current()->url << " rootItem: " << ( itu.current()->rootItem ? itu.current()->rootItem->url() : KURL() ) << " autoUpdates refcount: " << itu.current()->autoUpdates << " complete: " << itu.current()->complete << ( itu.current()->lstItems ? TQString(" with %1 items.").arg(itu.current()->lstItems->count()) : TQString(" lstItems=NULL") ) << endl; } kdDebug(7004) << "urlsCurrentlyHeld: " << endl; TQDictIterator< TQPtrList > it( urlsCurrentlyHeld ); for ( ; it.current() ; ++it ) { TQString list; for ( TQPtrListIterator listit( *it.current() ); listit.current(); ++listit ) list += " 0x" + TQString::number( (long)listit.current(), 16 ); kdDebug(7004) << " " << it.currentKey() << " " << it.current()->count() << " listers: " << list << endl; } kdDebug(7004) << "urlsCurrentlyListed: " << endl; TQDictIterator< TQPtrList > it2( urlsCurrentlyListed ); for ( ; it2.current() ; ++it2 ) { TQString list; for ( TQPtrListIterator listit( *it2.current() ); listit.current(); ++listit ) list += " 0x" + TQString::number( (long)listit.current(), 16 ); kdDebug(7004) << " " << it2.currentKey() << " " << it2.current()->count() << " listers: " << list << endl; } TQMap< TDEIO::ListJob *, TQValueList >::Iterator jit = jobs.begin(); kdDebug(7004) << "Jobs: " << endl; for ( ; jit != jobs.end() ; ++jit ) kdDebug(7004) << " " << jit.key() << " listing " << joburl( jit.key() ).prettyURL() << ": " << (*jit).count() << " entries." << endl; kdDebug(7004) << "Items in cache: " << endl; TQCacheIterator itc( itemsCached ); for ( ; itc.current() ; ++itc ) kdDebug(7004) << " " << itc.currentKey() << " rootItem: " << ( itc.current()->rootItem ? itc.current()->rootItem->url().prettyURL() : TQString("NULL") ) << ( itc.current()->lstItems ? TQString(" with %1 items.").arg(itc.current()->lstItems->count()) : TQString(" lstItems=NULL") ) << endl; } #endif /*********************** -- The new KDirLister -- ************************/ KDirLister::KDirLister( bool _delayedMimeTypes ) { kdDebug(7003) << "+KDirLister" << endl; d = new KDirListerPrivate; d->complete = true; d->delayedMimeTypes = _delayedMimeTypes; setAutoUpdate( true ); setDirOnlyMode( false ); setShowingDotFiles( false ); setAutoErrorHandlingEnabled( true, 0 ); } KDirLister::~KDirLister() { kdDebug(7003) << "-KDirLister" << endl; if ( KDirListerCache::exists() ) { // Stop all running jobs stop(); s_pCache->forgetDirs( this ); } delete d; } bool KDirLister::openURL( const KURL& _url, bool _keep, bool _reload ) { kdDebug(7003) << k_funcinfo << _url.prettyURL() << " keep=" << _keep << " reload=" << _reload << endl; // emit the current changes made to avoid an inconsistent treeview if ( d->changes != NONE && _keep ) emitChanges(); d->changes = NONE; // If a local path is available, monitor that instead of the given remote URL... KURL realURL = _url; if (!realURL.isLocalFile()) { TDEIO::LocalURLJob* localURLJob = TDEIO::localURL(_url); if (localURLJob) { connect(localURLJob, TQT_SIGNAL(localURL(TDEIO::Job*, const KURL&, bool)), this, TQT_SLOT(slotLocalURL(TDEIO::Job*, const KURL&, bool))); connect(localURLJob, TQT_SIGNAL(destroyed()), this, TQT_SLOT(slotLocalURLKIODestroyed())); d->localURLSlotFired = false; while (!d->localURLSlotFired) { tqApp->eventLoop()->processEvents(TQEventLoop::ExcludeUserInput); usleep(1000); } if (d->localURLResultIsLocal) { realURL = d->localURLResultURL; } } } return s_pCache->listDir( this, realURL, _keep, _reload ); } void KDirLister::slotLocalURL(TDEIO::Job *job, const KURL& url, bool isLocal) { d->localURLSlotFired = true; d->localURLResultURL = url; d->localURLResultIsLocal = isLocal; } void KDirLister::slotLocalURLKIODestroyed() { if (!d->localURLSlotFired) { d->localURLSlotFired = true; d->localURLResultURL = KURL(); d->localURLResultIsLocal = false; } } void KDirLister::stop() { kdDebug(7003) << k_funcinfo << endl; s_pCache->stop( this ); } void KDirLister::stop( const KURL& _url ) { kdDebug(7003) << k_funcinfo << _url.prettyURL() << endl; s_pCache->stop( this, _url ); } bool KDirLister::autoUpdate() const { return d->autoUpdate; } void KDirLister::setAutoUpdate( bool _enable ) { if ( d->autoUpdate == _enable ) return; d->autoUpdate = _enable; s_pCache->setAutoUpdate( this, _enable ); } bool KDirLister::showingDotFiles() const { return d->isShowingDotFiles; } void KDirLister::setShowingDotFiles( bool _showDotFiles ) { if ( d->isShowingDotFiles == _showDotFiles ) return; d->isShowingDotFiles = _showDotFiles; d->changes ^= DOT_FILES; } bool KDirLister::dirOnlyMode() const { return d->dirOnlyMode; } void KDirLister::setDirOnlyMode( bool _dirsOnly ) { if ( d->dirOnlyMode == _dirsOnly ) return; d->dirOnlyMode = _dirsOnly; d->changes ^= DIR_ONLY_MODE; } bool KDirLister::autoErrorHandlingEnabled() const { return d->autoErrorHandling; } void KDirLister::setAutoErrorHandlingEnabled( bool enable, TQWidget* parent ) { d->autoErrorHandling = enable; d->errorParent = parent; } const KURL& KDirLister::url() const { return d->url; } const KURL::List& KDirLister::directories() const { return d->lstDirs; } void KDirLister::emitChanges() { if ( d->changes == NONE ) return; static const TQString& dot = TDEGlobal::staticQString("."); static const TQString& dotdot = TDEGlobal::staticQString(".."); for ( KURL::List::Iterator it = d->lstDirs.begin(); it != d->lstDirs.end(); ++it ) { KFileItemListIterator kit( *s_pCache->itemsForDir( *it ) ); for ( ; kit.current(); ++kit ) { if ( (*kit)->text() == dot || (*kit)->text() == dotdot ) continue; bool oldMime = true, newMime = true; if ( d->changes & MIME_FILTER ) { oldMime = doMimeFilter( (*kit)->mimetype(), d->oldMimeFilter ) && doMimeExcludeFilter( (*kit)->mimetype(), d->oldMimeExcludeFilter ); newMime = doMimeFilter( (*kit)->mimetype(), d->mimeFilter ) && doMimeExcludeFilter( (*kit)->mimetype(), d->mimeExcludeFilter ); if ( oldMime && !newMime ) { emit deleteItem( *kit ); continue; } } if ( d->changes & DIR_ONLY_MODE ) { // the lister switched to dirOnlyMode if ( d->dirOnlyMode ) { if ( !(*kit)->isDir() ) emit deleteItem( *kit ); } else if ( !(*kit)->isDir() ) addNewItem( *kit ); continue; } if ( (*kit)->isHidden() ) { if ( d->changes & DOT_FILES ) { // the lister switched to dot files mode if ( d->isShowingDotFiles ) addNewItem( *kit ); else emit deleteItem( *kit ); continue; } } else if ( d->changes & NAME_FILTER ) { bool oldName = (*kit)->isDir() || d->oldFilters.isEmpty() || doNameFilter( (*kit)->text(), d->oldFilters ); bool newName = (*kit)->isDir() || d->lstFilters.isEmpty() || doNameFilter( (*kit)->text(), d->lstFilters ); if ( oldName && !newName ) { emit deleteItem( *kit ); continue; } else if ( !oldName && newName ) addNewItem( *kit ); } if ( (d->changes & MIME_FILTER) && !oldMime && newMime ) addNewItem( *kit ); } emitItems(); } d->changes = NONE; } void KDirLister::updateDirectory( const KURL& _u ) { s_pCache->updateDirectory( _u ); } bool KDirLister::isFinished() const { return d->complete; } KFileItem *KDirLister::rootItem() const { return d->rootFileItem; } KFileItem *KDirLister::findByURL( const KURL& _url ) const { return s_pCache->findByURL( this, _url ); } KFileItem *KDirLister::findByName( const TQString& _name ) const { return s_pCache->findByName( this, _name ); } #ifndef KDE_NO_COMPAT KFileItem *KDirLister::find( const KURL& _url ) const { return findByURL( _url ); } #endif // ================ public filter methods ================ // void KDirLister::setNameFilter( const TQString& nameFilter ) { if ( !(d->changes & NAME_FILTER) ) { d->oldFilters = d->lstFilters; d->lstFilters.setAutoDelete( false ); } d->lstFilters.clear(); d->lstFilters.setAutoDelete( true ); d->nameFilter = nameFilter; // Split on white space TQStringList list = TQStringList::split( ' ', nameFilter ); for ( TQStringList::Iterator it = list.begin(); it != list.end(); ++it ) d->lstFilters.append( new TQRegExp(*it, false, true ) ); d->changes |= NAME_FILTER; } const TQString& KDirLister::nameFilter() const { return d->nameFilter; } void KDirLister::setMimeFilter( const TQStringList& mimeFilter ) { if ( !(d->changes & MIME_FILTER) ) d->oldMimeFilter = d->mimeFilter; if ( mimeFilter.find("all/allfiles") != mimeFilter.end() || mimeFilter.find("all/all") != mimeFilter.end() ) d->mimeFilter.clear(); else d->mimeFilter = mimeFilter; d->changes |= MIME_FILTER; } void KDirLister::setMimeExcludeFilter( const TQStringList& mimeExcludeFilter ) { if ( !(d->changes & MIME_FILTER) ) d->oldMimeExcludeFilter = d->mimeExcludeFilter; d->mimeExcludeFilter = mimeExcludeFilter; d->changes |= MIME_FILTER; } void KDirLister::clearMimeFilter() { if ( !(d->changes & MIME_FILTER) ) { d->oldMimeFilter = d->mimeFilter; d->oldMimeExcludeFilter = d->mimeExcludeFilter; } d->mimeFilter.clear(); d->mimeExcludeFilter.clear(); d->changes |= MIME_FILTER; } const TQStringList& KDirLister::mimeFilters() const { return d->mimeFilter; } bool KDirLister::matchesFilter( const TQString& name ) const { return doNameFilter( name, d->lstFilters ); } bool KDirLister::matchesMimeFilter( const TQString& mime ) const { return doMimeFilter( mime, d->mimeFilter ) && doMimeExcludeFilter(mime,d->mimeExcludeFilter); } // ================ protected methods ================ // bool KDirLister::matchesFilter( const KFileItem *item ) const { Q_ASSERT( item ); static const TQString& dotdot = TDEGlobal::staticQString(".."); if ( item->text() == dotdot ) return false; if ( !d->isShowingDotFiles && item->isHidden() ) return false; if ( item->isDir() || d->lstFilters.isEmpty() ) return true; return matchesFilter( item->text() ); } bool KDirLister::matchesMimeFilter( const KFileItem *item ) const { Q_ASSERT( item ); // Don't lose time determining the mimetype if there is no filter if ( d->mimeFilter.isEmpty() && d->mimeExcludeFilter.isEmpty() ) return true; return matchesMimeFilter( item->mimetype() ); } bool KDirLister::doNameFilter( const TQString& name, const TQPtrList& filters ) const { for ( TQPtrListIterator it( filters ); it.current(); ++it ) if ( it.current()->exactMatch( name ) ) return true; return false; } bool KDirLister::doMimeFilter( const TQString& mime, const TQStringList& filters ) const { if ( filters.isEmpty() ) return true; KMimeType::Ptr mimeptr = KMimeType::mimeType(mime); //kdDebug(7004) << "doMimeFilter: investigating: "<name()<is(*it) ) return true; //else kdDebug(7004) << "doMimeFilter: compared without result to "<<*it<validURL( this, _url ); } void KDirLister::handleError( TDEIO::Job *job ) { if ( d->autoErrorHandling ) job->showErrorDialog( d->errorParent ); } // ================= private methods ================= // void KDirLister::addNewItem( const KFileItem *item ) { if ( ( d->dirOnlyMode && !item->isDir() ) || !matchesFilter( item ) ) return; // No reason to continue... bailing out here prevents a mimetype scan. if ( matchesMimeFilter( item ) ) { if ( !d->lstNewItems ) d->lstNewItems = new KFileItemList; d->lstNewItems->append( item ); // items not filtered } else { if ( !d->lstMimeFilteredItems ) d->lstMimeFilteredItems = new KFileItemList; d->lstMimeFilteredItems->append( item ); // only filtered by mime } } void KDirLister::addNewItems( const KFileItemList& items ) { // TODO: make this faster - test if we have a filter at all first // DF: was this profiled? The matchesFoo() functions should be fast, w/o filters... // Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good. // But that's for Qt4, not possible with TQPtrList. for ( KFileItemListIterator kit( items ); kit.current(); ++kit ) addNewItem( *kit ); } void KDirLister::aboutToRefreshItem( const KFileItem *item ) { // The code here follows the logic in addNewItem if ( ( d->dirOnlyMode && !item->isDir() ) || !matchesFilter( item ) ) d->refreshItemWasFiltered = true; else if ( !matchesMimeFilter( item ) ) d->refreshItemWasFiltered = true; else d->refreshItemWasFiltered = false; } void KDirLister::addRefreshItem( const KFileItem *item ) { bool isExcluded = (d->dirOnlyMode && !item->isDir()) || !matchesFilter( item ); if ( !isExcluded && matchesMimeFilter( item ) ) { if ( d->refreshItemWasFiltered ) { if ( !d->lstNewItems ) d->lstNewItems = new KFileItemList; d->lstNewItems->append( item ); } else { if ( !d->lstRefreshItems ) d->lstRefreshItems = new KFileItemList; d->lstRefreshItems->append( item ); } } else if ( !d->refreshItemWasFiltered ) { if ( !d->lstRemoveItems ) d->lstRemoveItems = new KFileItemList; // notify the user that the mimetype of a file changed that doesn't match // a filter or does match an exclude filter d->lstRemoveItems->append( item ); } } void KDirLister::emitItems() { KFileItemList *tmpNew = d->lstNewItems; d->lstNewItems = 0; KFileItemList *tmpMime = d->lstMimeFilteredItems; d->lstMimeFilteredItems = 0; KFileItemList *tmpRefresh = d->lstRefreshItems; d->lstRefreshItems = 0; KFileItemList *tmpRemove = d->lstRemoveItems; d->lstRemoveItems = 0; if ( tmpNew ) { emit newItems( *tmpNew ); delete tmpNew; } if ( tmpMime ) { emit itemsFilteredByMime( *tmpMime ); delete tmpMime; } if ( tmpRefresh ) { emit refreshItems( *tmpRefresh ); delete tmpRefresh; } if ( tmpRemove ) { for ( KFileItem *tmp = tmpRemove->first(); tmp; tmp = tmpRemove->next() ) emit deleteItem( tmp ); delete tmpRemove; } } void KDirLister::emitDeleteItem( KFileItem *item ) { if ( ( d->dirOnlyMode && !item->isDir() ) || !matchesFilter( item ) ) return; // No reason to continue... bailing out here prevents a mimetype scan. if ( matchesMimeFilter( item ) ) emit deleteItem( item ); } // ================ private slots ================ // void KDirLister::slotInfoMessage( TDEIO::Job *, const TQString& message ) { emit infoMessage( message ); } void KDirLister::slotPercent( TDEIO::Job *job, unsigned long pcnt ) { d->jobData[static_cast(job)].percent = pcnt; int result = 0; TDEIO::filesize_t size = 0; TQMap< TDEIO::ListJob *, KDirListerPrivate::JobData >::Iterator dataIt = d->jobData.begin(); while ( dataIt != d->jobData.end() ) { result += (*dataIt).percent * (*dataIt).totalSize; size += (*dataIt).totalSize; ++dataIt; } if ( size != 0 ) result /= size; else result = 100; emit percent( result ); } void KDirLister::slotTotalSize( TDEIO::Job *job, TDEIO::filesize_t size ) { d->jobData[static_cast(job)].totalSize = size; TDEIO::filesize_t result = 0; TQMap< TDEIO::ListJob *, KDirListerPrivate::JobData >::Iterator dataIt = d->jobData.begin(); while ( dataIt != d->jobData.end() ) { result += (*dataIt).totalSize; ++dataIt; } emit totalSize( result ); } void KDirLister::slotProcessedSize( TDEIO::Job *job, TDEIO::filesize_t size ) { d->jobData[static_cast(job)].processedSize = size; TDEIO::filesize_t result = 0; TQMap< TDEIO::ListJob *, KDirListerPrivate::JobData >::Iterator dataIt = d->jobData.begin(); while ( dataIt != d->jobData.end() ) { result += (*dataIt).processedSize; ++dataIt; } emit processedSize( result ); } void KDirLister::slotSpeed( TDEIO::Job *job, unsigned long spd ) { d->jobData[static_cast(job)].speed = spd; int result = 0; TQMap< TDEIO::ListJob *, KDirListerPrivate::JobData >::Iterator dataIt = d->jobData.begin(); while ( dataIt != d->jobData.end() ) { result += (*dataIt).speed; ++dataIt; } emit speed( result ); } uint KDirLister::numJobs() { return d->jobData.count(); } void KDirLister::jobDone( TDEIO::ListJob *job ) { d->jobData.remove( job ); } void KDirLister::jobStarted( TDEIO::ListJob *job ) { KDirListerPrivate::JobData jobData; jobData.speed = 0; jobData.percent = 0; jobData.processedSize = 0; jobData.totalSize = 0; d->jobData.insert( job, jobData ); d->complete = false; } void KDirLister::connectJob( TDEIO::ListJob *job ) { connect( job, TQT_SIGNAL(infoMessage( TDEIO::Job *, const TQString& )), this, TQT_SLOT(slotInfoMessage( TDEIO::Job *, const TQString& )) ); connect( job, TQT_SIGNAL(percent( TDEIO::Job *, unsigned long )), this, TQT_SLOT(slotPercent( TDEIO::Job *, unsigned long )) ); connect( job, TQT_SIGNAL(totalSize( TDEIO::Job *, TDEIO::filesize_t )), this, TQT_SLOT(slotTotalSize( TDEIO::Job *, TDEIO::filesize_t )) ); connect( job, TQT_SIGNAL(processedSize( TDEIO::Job *, TDEIO::filesize_t )), this, TQT_SLOT(slotProcessedSize( TDEIO::Job *, TDEIO::filesize_t )) ); connect( job, TQT_SIGNAL(speed( TDEIO::Job *, unsigned long )), this, TQT_SLOT(slotSpeed( TDEIO::Job *, unsigned long )) ); } void KDirLister::setMainWindow( TQWidget *window ) { d->window = window; } TQWidget *KDirLister::mainWindow() { return d->window; } KFileItemList KDirLister::items( WhichItems which ) const { return itemsForDir( url(), which ); } KFileItemList KDirLister::itemsForDir( const KURL& dir, WhichItems which ) const { KFileItemList result; KFileItemList *allItems = s_pCache->itemsForDir( dir ); if ( !allItems ) return result; if ( which == AllItems ) result = *allItems; // shallow copy else // only items passing the filters { for ( KFileItemListIterator kit( *allItems ); kit.current(); ++kit ) { KFileItem *item = *kit; bool isExcluded = (d->dirOnlyMode && !item->isDir()) || !matchesFilter( item ); if ( !isExcluded && matchesMimeFilter( item ) ) result.append( item ); } } return result; } // to keep BC changes void KDirLister::virtual_hook( int, void * ) { /*BASE::virtual_hook( id, data );*/ } #include "kdirlister.moc" #include "kdirlister_p.moc"