kdirlister.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
00003                  2000 Carsten Pfeiffer <pfeiffer@kde.org>
00004                  2001-2005 Michael Brade <brade@kde.org>
00005 
00006    This library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License as published by the Free Software Foundation; either
00009    version 2 of the License, or (at your option) any later version.
00010 
00011    This library is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Library General Public License for more details.
00015 
00016    You should have received a copy of the GNU Library General Public License
00017    along with this library; see the file COPYING.LIB.  If not, write to
00018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019    Boston, MA 02110-1301, USA.
00020 */
00021 
00022 #include "kdirlister.h"
00023 
00024 #include <qregexp.h>
00025 #include <qptrlist.h>
00026 #include <qtimer.h>
00027 
00028 #include <kapplication.h>
00029 #include <kdebug.h>
00030 #include <klocale.h>
00031 #include <kio/job.h>
00032 #include <kmessagebox.h>
00033 #include <kglobal.h>
00034 #include <kglobalsettings.h>
00035 #include <kstaticdeleter.h>
00036 #include <kprotocolinfo.h>
00037 
00038 #include "kdirlister_p.h"
00039 
00040 #include <assert.h>
00041 
00042 KDirListerCache* KDirListerCache::s_pSelf = 0;
00043 static KStaticDeleter<KDirListerCache> sd_KDirListerCache;
00044 
00045 // Enable this to get printDebug() called often, to see the contents of the cache
00046 //#define DEBUG_CACHE
00047 
00048 // Make really sure it doesn't get activated in the final build
00049 #ifdef NDEBUG
00050 #undef DEBUG_CACHE
00051 #endif
00052 
00053 KDirListerCache::KDirListerCache( int maxCount )
00054   : itemsCached( maxCount )
00055 {
00056   kdDebug(7004) << "+KDirListerCache" << endl;
00057 
00058   itemsInUse.setAutoDelete( false );
00059   itemsCached.setAutoDelete( true );
00060   urlsCurrentlyListed.setAutoDelete( true );
00061   urlsCurrentlyHeld.setAutoDelete( true );
00062   pendingUpdates.setAutoDelete( true );
00063 
00064   connect( kdirwatch, SIGNAL( dirty( const QString& ) ),
00065            this, SLOT( slotFileDirty( const QString& ) ) );
00066   connect( kdirwatch, SIGNAL( created( const QString& ) ),
00067            this, SLOT( slotFileCreated( const QString& ) ) );
00068   connect( kdirwatch, SIGNAL( deleted( const QString& ) ),
00069            this, SLOT( slotFileDeleted( const QString& ) ) );
00070 }
00071 
00072 KDirListerCache::~KDirListerCache()
00073 {
00074   kdDebug(7004) << "-KDirListerCache" << endl;
00075 
00076   itemsInUse.setAutoDelete( true );
00077   itemsInUse.clear();
00078   itemsCached.clear();
00079   urlsCurrentlyListed.clear();
00080   urlsCurrentlyHeld.clear();
00081 
00082   if ( KDirWatch::exists() )
00083     kdirwatch->disconnect( this );
00084 }
00085 
00086 // setting _reload to true will emit the old files and
00087 // call updateDirectory
00088 bool KDirListerCache::listDir( KDirLister *lister, const KURL& _u,
00089                                bool _keep, bool _reload )
00090 {
00091   // like this we don't have to worry about trailing slashes any further
00092   KURL _url = _u;
00093   _url.cleanPath(); // kill consecutive slashes
00094   _url.adjustPath(-1);
00095   QString urlStr = _url.url();
00096 
00097   if ( !lister->validURL( _url ) )
00098     return false;
00099 
00100 #ifdef DEBUG_CACHE
00101   printDebug();
00102 #endif
00103   kdDebug(7004) << k_funcinfo << lister << " url=" << _url
00104                 << " keep=" << _keep << " reload=" << _reload << endl;
00105 
00106   if ( !_keep )
00107   {
00108     // stop any running jobs for lister
00109     stop( lister );
00110 
00111     // clear our internal list for lister
00112     forgetDirs( lister );
00113 
00114     lister->d->rootFileItem = 0;
00115   }
00116   else if ( lister->d->lstDirs.find( _url ) != lister->d->lstDirs.end() )
00117   {
00118     // stop the job listing _url for this lister
00119     stop( lister, _url );
00120 
00121     // clear _url for lister
00122     forgetDirs( lister, _url, true );
00123 
00124     if ( lister->d->url == _url )
00125       lister->d->rootFileItem = 0;
00126   }
00127 
00128   lister->d->lstDirs.append( _url );
00129 
00130   if ( lister->d->url.isEmpty() || !_keep ) // set toplevel URL only if not set yet
00131     lister->d->url = _url;
00132 
00133   DirItem *itemU = itemsInUse[urlStr];
00134   DirItem *itemC;
00135 
00136   if ( !urlsCurrentlyListed[urlStr] )
00137   {
00138     // if there is an update running for _url already we get into
00139     // the following case - it will just be restarted by updateDirectory().
00140 
00141     if ( itemU )
00142     {
00143       kdDebug(7004) << "listDir: Entry already in use: " << _url << endl;
00144 
00145       bool oldState = lister->d->complete;
00146       lister->d->complete = false;
00147 
00148       emit lister->started( _url );
00149 
00150       if ( !lister->d->rootFileItem && lister->d->url == _url )
00151         lister->d->rootFileItem = itemU->rootItem;
00152 
00153       lister->addNewItems( *(itemU->lstItems) );
00154       lister->emitItems();
00155 
00156       // _url is already in use, so there is already an entry in urlsCurrentlyHeld
00157       assert( urlsCurrentlyHeld[urlStr] );
00158       urlsCurrentlyHeld[urlStr]->append( lister );
00159 
00160       lister->d->complete = oldState;
00161 
00162       emit lister->completed( _url );
00163       if ( lister->d->complete )
00164         emit lister->completed();
00165 
00166       if ( _reload || !itemU->complete )
00167         updateDirectory( _url );
00168     }
00169     else if ( !_reload && (itemC = itemsCached.take( urlStr )) )
00170     {
00171       kdDebug(7004) << "listDir: Entry in cache: " << _url << endl;
00172 
00173       itemC->decAutoUpdate();
00174       itemsInUse.insert( urlStr, itemC );
00175       itemU = itemC;
00176 
00177       bool oldState = lister->d->complete;
00178       lister->d->complete = false;
00179 
00180       emit lister->started( _url );
00181 
00182       if ( !lister->d->rootFileItem && lister->d->url == _url )
00183         lister->d->rootFileItem = itemC->rootItem;
00184 
00185       lister->addNewItems( *(itemC->lstItems) );
00186       lister->emitItems();
00187 
00188       Q_ASSERT( !urlsCurrentlyHeld[urlStr] );
00189       QPtrList<KDirLister> *list = new QPtrList<KDirLister>;
00190       list->append( lister );
00191       urlsCurrentlyHeld.insert( urlStr, list );
00192 
00193       lister->d->complete = oldState;
00194 
00195       emit lister->completed( _url );
00196       if ( lister->d->complete )
00197         emit lister->completed();
00198 
00199       if ( !itemC->complete )
00200         updateDirectory( _url );
00201     }
00202     else  // dir not in cache or _reload is true
00203     {
00204       kdDebug(7004) << "listDir: Entry not in cache or reloaded: " << _url << endl;
00205 
00206       QPtrList<KDirLister> *list = new QPtrList<KDirLister>;
00207       list->append( lister );
00208       urlsCurrentlyListed.insert( urlStr, list );
00209 
00210       itemsCached.remove( urlStr );
00211       itemU = new DirItem( _url );
00212       itemsInUse.insert( urlStr, itemU );
00213 
00214 //        // we have a limit of MAX_JOBS_PER_LISTER concurrently running jobs
00215 //        if ( lister->numJobs() >= MAX_JOBS_PER_LISTER )
00216 //        {
00217 //          lstPendingUpdates.append( _url );
00218 //        }
00219 //        else
00220 //        {
00221 
00222       if ( lister->d->url == _url )
00223         lister->d->rootFileItem = 0;
00224 
00225       KIO::ListJob* job = KIO::listDir( _url, false /* no default GUI */ );
00226       jobs.insert( job, QValueList<KIO::UDSEntry>() );
00227 
00228       lister->jobStarted( job );
00229       lister->connectJob( job );
00230 
00231       if ( lister->d->window )
00232         job->setWindow( lister->d->window );
00233 
00234       connect( job, SIGNAL( entries( KIO::Job *, const KIO::UDSEntryList & ) ),
00235                this, SLOT( slotEntries( KIO::Job *, const KIO::UDSEntryList & ) ) );
00236       connect( job, SIGNAL( result( KIO::Job * ) ),
00237                this, SLOT( slotResult( KIO::Job * ) ) );
00238       connect( job, SIGNAL( redirection( KIO::Job *, const KURL & ) ),
00239                this, SLOT( slotRedirection( KIO::Job *, const KURL & ) ) );
00240 
00241       emit lister->started( _url );
00242 
00243 //        }
00244     }
00245   }
00246   else
00247   {
00248     kdDebug(7004) << "listDir: Entry currently being listed: " << _url << endl;
00249 
00250     emit lister->started( _url );
00251 
00252     urlsCurrentlyListed[urlStr]->append( lister );
00253 
00254     KIO::ListJob *job = jobForUrl( urlStr );
00255     Q_ASSERT( job );
00256 
00257     lister->jobStarted( job );
00258     lister->connectJob( job );
00259 
00260     Q_ASSERT( itemU );
00261 
00262     if ( !lister->d->rootFileItem && lister->d->url == _url )
00263       lister->d->rootFileItem = itemU->rootItem;
00264 
00265     lister->addNewItems( *(itemU->lstItems) );
00266     lister->emitItems();
00267   }
00268 
00269   // automatic updating of directories
00270   if ( lister->d->autoUpdate )
00271     itemU->incAutoUpdate();
00272 
00273   return true;
00274 }
00275 
00276 bool KDirListerCache::validURL( const KDirLister *lister, const KURL& url ) const
00277 {
00278   if ( !url.isValid() )
00279   {
00280     if ( lister->d->autoErrorHandling )
00281     {
00282       QString tmp = i18n("Malformed URL\n%1").arg( url.prettyURL() );
00283       KMessageBox::error( lister->d->errorParent, tmp );
00284     }
00285     return false;
00286   }
00287 
00288   if ( !KProtocolInfo::supportsListing( url ) )
00289   {
00290     if ( lister->d->autoErrorHandling )
00291     {
00292       // TODO: this message should be changed during next string unfreeze!
00293       QString tmp = i18n("Malformed URL\n%1").arg( url.prettyURL() );
00294       KMessageBox::error( lister->d->errorParent, tmp );
00295     }
00296     return false;
00297   }
00298 
00299   return true;
00300 }
00301 
00302 void KDirListerCache::stop( KDirLister *lister )
00303 {
00304 #ifdef DEBUG_CACHE
00305   printDebug();
00306 #endif
00307   kdDebug(7004) << k_funcinfo << "lister: " << lister << endl;
00308   bool stopped = false;
00309 
00310   QDictIterator< QPtrList<KDirLister> > it( urlsCurrentlyListed );
00311   QPtrList<KDirLister> *listers;
00312   while ( (listers = it.current()) )
00313   {
00314     if ( listers->findRef( lister ) > -1 )
00315     {
00316       // lister is listing url
00317       QString url = it.currentKey();
00318 
00319       //kdDebug(7004) << k_funcinfo << " found lister in list - for " << url << endl;
00320       bool ret = listers->removeRef( lister );
00321       Q_ASSERT( ret );
00322       
00323       KIO::ListJob *job = jobForUrl( url );
00324       if ( job )
00325         lister->jobDone( job );
00326 
00327       // move lister to urlsCurrentlyHeld
00328       QPtrList<KDirLister> *holders = urlsCurrentlyHeld[url];
00329       if ( !holders )
00330       {
00331         holders = new QPtrList<KDirLister>;
00332         urlsCurrentlyHeld.insert( url, holders );
00333       }
00334 
00335       holders->append( lister );
00336 
00337       emit lister->canceled( KURL( url ) );
00338 
00339       //kdDebug(7004) << k_funcinfo << "remaining list: " << listers->count() << " listers" << endl;
00340 
00341       if ( listers->isEmpty() )
00342       {
00343         // kill the job since it isn't used any more
00344         if ( job )
00345           killJob( job );
00346 
00347         urlsCurrentlyListed.remove( url );
00348       }
00349 
00350       stopped = true;
00351     }
00352     else
00353       ++it;
00354   }
00355 
00356   if ( stopped )
00357   {
00358     emit lister->canceled();
00359     lister->d->complete = true;
00360   }
00361 
00362   // this is wrong if there is still an update running!
00363   //Q_ASSERT( lister->d->complete );
00364 }
00365 
00366 void KDirListerCache::stop( KDirLister *lister, const KURL& _u )
00367 {
00368   QString urlStr( _u.url(-1) );
00369   KURL _url( urlStr );
00370 
00371   // TODO: consider to stop all the "child jobs" of _url as well
00372   kdDebug(7004) << k_funcinfo << lister << " url=" << _url << endl;
00373 
00374   QPtrList<KDirLister> *listers = urlsCurrentlyListed[urlStr];
00375   if ( !listers || !listers->removeRef( lister ) )
00376     return;
00377 
00378   // move lister to urlsCurrentlyHeld
00379   QPtrList<KDirLister> *holders = urlsCurrentlyHeld[urlStr];
00380   if ( !holders )
00381   {
00382     holders = new QPtrList<KDirLister>;
00383     urlsCurrentlyHeld.insert( urlStr, holders );
00384   }
00385 
00386   holders->append( lister );
00387 
00388 
00389   KIO::ListJob *job = jobForUrl( urlStr );
00390   if ( job )
00391     lister->jobDone( job );
00392 
00393   emit lister->canceled( _url );
00394 
00395   if ( listers->isEmpty() )
00396   {
00397     // kill the job since it isn't used any more
00398     if ( job )
00399       killJob( job );
00400 
00401     urlsCurrentlyListed.remove( urlStr );
00402   }
00403 
00404   if ( lister->numJobs() == 0 )
00405   {
00406     lister->d->complete = true;
00407 
00408     // we killed the last job for lister
00409     emit lister->canceled();
00410   }
00411 }
00412 
00413 void KDirListerCache::setAutoUpdate( KDirLister *lister, bool enable )
00414 {
00415   // IMPORTANT: this method does not check for the current autoUpdate state!
00416 
00417   for ( KURL::List::Iterator it = lister->d->lstDirs.begin();
00418         it != lister->d->lstDirs.end(); ++it )
00419   {
00420     if ( enable )
00421       itemsInUse[(*it).url()]->incAutoUpdate();
00422     else
00423       itemsInUse[(*it).url()]->decAutoUpdate();
00424   }
00425 }
00426 
00427 void KDirListerCache::forgetDirs( KDirLister *lister )
00428 {
00429   kdDebug(7004) << k_funcinfo << lister << endl;
00430 
00431   emit lister->clear();
00432 
00433   // forgetDirs() will modify lstDirs, make a copy first
00434   KURL::List lstDirsCopy = lister->d->lstDirs;
00435   for ( KURL::List::Iterator it = lstDirsCopy.begin();
00436         it != lstDirsCopy.end(); ++it )
00437   {
00438     forgetDirs( lister, *it, false );
00439   }
00440 }
00441 
00442 void KDirListerCache::forgetDirs( KDirLister *lister, const KURL& _url, bool notify )
00443 {
00444   kdDebug(7004) << k_funcinfo << lister << " _url: " << _url << endl;
00445 
00446   KURL url( _url );
00447   url.adjustPath( -1 );
00448   QString urlStr = url.url();
00449   QPtrList<KDirLister> *holders = urlsCurrentlyHeld[urlStr];
00450   Q_ASSERT( holders );
00451   holders->removeRef( lister );
00452 
00453   // remove the dir from lister->d->lstDirs so that it doesn't contain things
00454   // that itemsInUse doesn't. When emitting the canceled signals lstDirs must
00455   // not contain anything that itemsInUse does not contain. (otherwise it 
00456   // might crash in findByName()).
00457   lister->d->lstDirs.remove( lister->d->lstDirs.find( url ) );
00458 
00459   DirItem *item = itemsInUse[urlStr];
00460   Q_ASSERT( item );
00461 
00462   if ( holders->isEmpty() )
00463   {
00464     urlsCurrentlyHeld.remove( urlStr ); // this deletes the (empty) holders list
00465     if ( !urlsCurrentlyListed[urlStr] )
00466     {
00467       // item not in use anymore -> move into cache if complete
00468       itemsInUse.remove( urlStr );
00469 
00470       // this job is a running update
00471       KIO::ListJob *job = jobForUrl( urlStr );
00472       if ( job )
00473       {
00474         lister->jobDone( job );
00475         killJob( job );
00476         kdDebug(7004) << k_funcinfo << "Killing update job for " << urlStr << endl;
00477 
00478         emit lister->canceled( url );
00479         if ( lister->numJobs() == 0 )
00480         {
00481           lister->d->complete = true;
00482           emit lister->canceled();
00483         }
00484       }
00485 
00486       if ( notify )
00487         emit lister->clear( url );
00488 
00489       if ( item->complete )
00490       {
00491         kdDebug(7004) << k_funcinfo << lister << " item moved into cache: " << url << endl;
00492         itemsCached.insert( urlStr, item ); // TODO: may return false!!
00493 
00494         // Should we forget the dir for good, or keep a watch on it?
00495         // Generally keep a watch, except when it would prevent
00496         // unmounting a removable device (#37780)
00497         const bool isLocal = item->url.isLocalFile();
00498         const bool isManuallyMounted = isLocal && KIO::manually_mounted( item->url.path() );
00499         bool containsManuallyMounted = false;
00500         if ( !isManuallyMounted && item->lstItems && isLocal ) 
00501         {
00502           // Look for a manually-mounted directory inside
00503           // If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM
00504           // I hope this isn't too slow (manually_mounted caches the last device so most
00505           // of the time this is just a stat per subdir)
00506           KFileItemListIterator kit( *item->lstItems );
00507           for ( ; kit.current() && !containsManuallyMounted; ++kit )
00508             if ( (*kit)->isDir() && KIO::manually_mounted( (*kit)->url().path() ) )
00509               containsManuallyMounted = true;
00510         }
00511 
00512         if ( isManuallyMounted || containsManuallyMounted ) 
00513         {
00514           kdDebug(7004) << "Not adding a watch on " << item->url << " because it " <<
00515             ( isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir" ) << endl;
00516           item->complete = false; // set to "dirty"
00517         }
00518         else
00519           item->incAutoUpdate(); // keep watch
00520       }
00521       else
00522       {
00523         delete item;
00524         item = 0;
00525       }
00526     }
00527   }
00528 
00529   if ( item && lister->d->autoUpdate )
00530     item->decAutoUpdate();
00531 }
00532 
00533 void KDirListerCache::updateDirectory( const KURL& _dir )
00534 {
00535   kdDebug(7004) << k_funcinfo << _dir << endl;
00536 
00537   QString urlStr = _dir.url(-1);
00538   if ( !checkUpdate( urlStr ) )
00539     return;
00540 
00541   // A job can be running to
00542   //   - only list a new directory: the listers are in urlsCurrentlyListed
00543   //   - only update a directory: the listers are in urlsCurrentlyHeld
00544   //   - update a currently running listing: the listers are in urlsCurrentlyListed
00545   //     and urlsCurrentlyHeld
00546 
00547   QPtrList<KDirLister> *listers = urlsCurrentlyListed[urlStr];
00548   QPtrList<KDirLister> *holders = urlsCurrentlyHeld[urlStr];
00549 
00550   // restart the job for _dir if it is running already
00551   bool killed = false;
00552   QWidget *window = 0;
00553   KIO::ListJob *job = jobForUrl( urlStr );
00554   if ( job )
00555   {
00556      window = job->window();
00557 
00558      killJob( job );
00559      killed = true;
00560 
00561      if ( listers )
00562         for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00563            kdl->jobDone( job );
00564 
00565      if ( holders )
00566         for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
00567            kdl->jobDone( job );
00568   }
00569   kdDebug(7004) << k_funcinfo << "Killed = " << killed << endl;
00570 
00571   // we don't need to emit canceled signals since we only replaced the job,
00572   // the listing is continuing.
00573 
00574   Q_ASSERT( !listers || (listers && killed) );
00575 
00576   job = KIO::listDir( _dir, false /* no default GUI */ );
00577   jobs.insert( job, QValueList<KIO::UDSEntry>() );
00578 
00579   connect( job, SIGNAL(entries( KIO::Job *, const KIO::UDSEntryList & )),
00580            this, SLOT(slotUpdateEntries( KIO::Job *, const KIO::UDSEntryList & )) );
00581   connect( job, SIGNAL(result( KIO::Job * )),
00582            this, SLOT(slotUpdateResult( KIO::Job * )) );
00583 
00584   kdDebug(7004) << k_funcinfo << "update started in " << _dir << endl;
00585 
00586   if ( listers )
00587      for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00588         kdl->jobStarted( job );
00589 
00590   if ( holders )
00591   {
00592      if ( !killed )
00593      {
00594         bool first = true;
00595         for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
00596         {
00597            kdl->jobStarted( job );
00598            if ( first && kdl->d->window )
00599            {
00600               first = false;
00601               job->setWindow( kdl->d->window );
00602            }
00603            emit kdl->started( _dir );
00604         }
00605      }
00606      else
00607      {
00608         job->setWindow( window );
00609 
00610         for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
00611            kdl->jobStarted( job );
00612      }
00613   }
00614 }
00615 
00616 bool KDirListerCache::checkUpdate( const QString& _dir )
00617 {
00618   if ( !itemsInUse[_dir] )
00619   {
00620     DirItem *item = itemsCached[_dir];
00621     if ( item && item->complete )
00622     {
00623       item->complete = false;
00624       item->decAutoUpdate();
00625       // Hmm, this debug output might include login/password from the _dir URL.
00626       //kdDebug(7004) << k_funcinfo << "directory " << _dir << " not in use, marked dirty." << endl;
00627     }
00628     //else
00629       //kdDebug(7004) << k_funcinfo << "aborted, directory " << _dir << " not in cache." << endl;
00630 
00631     return false;
00632   }
00633   else
00634     return true;
00635 }
00636 
00637 KFileItemList *KDirListerCache::itemsForDir( const KURL &_dir ) const
00638 {
00639   QString urlStr = _dir.url(-1);
00640   DirItem *item = itemsInUse[ urlStr ];
00641   if ( !item )
00642     item = itemsCached[ urlStr ];
00643   return item ? item->lstItems : 0;
00644 }
00645 
00646 KFileItem *KDirListerCache::findByName( const KDirLister *lister, const QString& _name ) const
00647 {
00648   Q_ASSERT( lister );
00649 
00650   for ( KURL::List::Iterator it = lister->d->lstDirs.begin();
00651         it != lister->d->lstDirs.end(); ++it )
00652   {
00653     KFileItemListIterator kit( *itemsInUse[(*it).url()]->lstItems );
00654     for ( ; kit.current(); ++kit )
00655       if ( (*kit)->name() == _name )
00656         return (*kit);
00657   }
00658 
00659   return 0L;
00660 }
00661 
00662 KFileItem *KDirListerCache::findByURL( const KDirLister *lister, const KURL& _u ) const
00663 {
00664   KURL _url = _u;
00665   _url.adjustPath(-1);
00666 
00667   KURL parentDir( _url );
00668   parentDir.setPath( parentDir.directory() );
00669 
00670   // If lister is set, check that it contains this dir
00671   if ( lister && !lister->d->lstDirs.contains( parentDir ) )
00672       return 0L;
00673 
00674   KFileItemList *itemList = itemsForDir( parentDir );
00675   if ( itemList )
00676   {
00677     KFileItemListIterator kit( *itemList );
00678     for ( ; kit.current(); ++kit )
00679       if ( (*kit)->url() == _url )
00680         return (*kit);
00681   }
00682   return 0L;
00683 }
00684 
00685 void KDirListerCache::FilesAdded( const KURL &dir )
00686 {
00687   kdDebug(7004) << k_funcinfo << dir << endl;
00688   updateDirectory( dir );
00689 }
00690 
00691 void KDirListerCache::FilesRemoved( const KURL::List &fileList )
00692 {
00693   kdDebug(7004) << k_funcinfo << endl;
00694   KURL::List::ConstIterator it = fileList.begin();
00695   for ( ; it != fileList.end() ; ++it )
00696   {
00697     // emit the deleteItem signal if this file was shown in any view
00698     KFileItem *fileitem = 0L;
00699     KURL parentDir( *it );
00700     parentDir.setPath( parentDir.directory() );
00701     KFileItemList *lstItems = itemsForDir( parentDir );
00702     if ( lstItems )
00703     {
00704       KFileItem *fit = lstItems->first();
00705       for ( ; fit; fit = lstItems->next() )
00706         if ( fit->url() == *it ) {
00707           fileitem = fit;
00708           lstItems->take(); // remove fileitem from list
00709           break;
00710         }
00711     }
00712 
00713     // Tell the views about it before deleting the KFileItems. They might need the subdirs'
00714     // file items (see the dirtree).
00715     if ( fileitem )
00716     {
00717       QPtrList<KDirLister> *listers = urlsCurrentlyHeld[parentDir.url()];
00718       if ( listers )
00719         for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00720           kdl->emitDeleteItem( fileitem );
00721     }
00722 
00723     // If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case.
00724     if ( !fileitem || fileitem->isDir() )
00725     {
00726       // in case of a dir, check if we have any known children, there's much to do in that case
00727       // (stopping jobs, removing dirs from cache etc.)
00728       deleteDir( *it );
00729     }
00730 
00731     // now remove the item itself
00732     delete fileitem;
00733   }
00734 }
00735 
00736 void KDirListerCache::FilesChanged( const KURL::List &fileList )
00737 {
00738   KURL::List dirsToUpdate;
00739   kdDebug(7004) << k_funcinfo << "only half implemented" << endl;
00740   KURL::List::ConstIterator it = fileList.begin();
00741   for ( ; it != fileList.end() ; ++it )
00742   {
00743     if ( ( *it ).isLocalFile() )
00744     {
00745       kdDebug(7004) << "KDirListerCache::FilesChanged " << *it << endl;
00746       KFileItem *fileitem = findByURL( 0, *it );
00747       if ( fileitem )
00748       {
00749           // we need to refresh the item, because e.g. the permissions can have changed.
00750           aboutToRefreshItem( fileitem );
00751           fileitem->refresh();
00752           emitRefreshItem( fileitem );
00753       }
00754       else
00755           kdDebug(7004) << "item not found" << endl;
00756     } else {
00757       // For remote files, refresh() won't be able to figure out the new information.
00758       // Let's update the dir.
00759       KURL dir( *it );
00760       dir.setPath( dir.directory( true ) );
00761       if ( dirsToUpdate.find( dir ) == dirsToUpdate.end() )
00762         dirsToUpdate.prepend( dir );
00763     }
00764   }
00765 
00766   KURL::List::ConstIterator itdir = dirsToUpdate.begin();
00767   for ( ; itdir != dirsToUpdate.end() ; ++itdir )
00768     updateDirectory( *itdir );
00769   // ## TODO problems with current jobs listing/updating that dir
00770   // ( see kde-2.2.2's kdirlister )
00771 }
00772 
00773 void KDirListerCache::FileRenamed( const KURL &src, const KURL &dst )
00774 {
00775   kdDebug(7004) << k_funcinfo << src.prettyURL() << " -> " << dst.prettyURL() << endl;
00776 #ifdef DEBUG_CACHE
00777   printDebug();
00778 #endif
00779 
00780   // Somehow this should only be called if src is a dir. But how could we know if it is?
00781   // (Note that looking into itemsInUse isn't good enough. One could rename a subdir in a view.)
00782   renameDir( src, dst );
00783 
00784   // Now update the KFileItem representing that file or dir (not exclusive with the above!)
00785   KURL oldurl( src );
00786   oldurl.adjustPath( -1 );
00787   KFileItem *fileitem = findByURL( 0, oldurl );
00788   if ( fileitem )
00789   {
00790     if ( !fileitem->isLocalFile() && !fileitem->localPath().isEmpty() ) // it uses UDS_LOCAL_PATH? ouch, needs an update then
00791         FilesChanged( src );
00792     else
00793     {
00794         aboutToRefreshItem( fileitem );
00795         fileitem->setURL( dst );
00796         fileitem->refreshMimeType();
00797         emitRefreshItem( fileitem );
00798     }
00799   }
00800 #ifdef DEBUG_CACHE
00801   printDebug();
00802 #endif
00803 }
00804 
00805 void KDirListerCache::aboutToRefreshItem( KFileItem *fileitem )
00806 {
00807   // Look whether this item was shown in any view, i.e. held by any dirlister
00808   KURL parentDir( fileitem->url() );
00809   parentDir.setPath( parentDir.directory() );
00810   QString parentDirURL = parentDir.url();
00811   QPtrList<KDirLister> *listers = urlsCurrentlyHeld[parentDirURL];
00812   if ( listers )
00813     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00814       kdl->aboutToRefreshItem( fileitem );
00815 
00816   // Also look in urlsCurrentlyListed, in case the user manages to rename during a listing
00817   listers = urlsCurrentlyListed[parentDirURL];
00818   if ( listers )
00819     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00820       kdl->aboutToRefreshItem( fileitem );
00821 }
00822 
00823 void KDirListerCache::emitRefreshItem( KFileItem *fileitem )
00824 {
00825   // Look whether this item was shown in any view, i.e. held by any dirlister
00826   KURL parentDir( fileitem->url() );
00827   parentDir.setPath( parentDir.directory() );
00828   QString parentDirURL = parentDir.url();
00829   QPtrList<KDirLister> *listers = urlsCurrentlyHeld[parentDirURL];
00830   if ( listers )
00831     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00832     {
00833       kdl->addRefreshItem( fileitem );
00834       kdl->emitItems();
00835     }
00836 
00837   // Also look in urlsCurrentlyListed, in case the user manages to rename during a listing
00838   listers = urlsCurrentlyListed[parentDirURL];
00839   if ( listers )
00840     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00841     {
00842       kdl->addRefreshItem( fileitem );
00843       kdl->emitItems();
00844     }
00845 }
00846 
00847 KDirListerCache* KDirListerCache::self()
00848 {
00849   if ( !s_pSelf )
00850     s_pSelf = sd_KDirListerCache.setObject( s_pSelf, new KDirListerCache );
00851 
00852   return s_pSelf;
00853 }
00854 
00855 bool KDirListerCache::exists()
00856 {
00857   return s_pSelf != 0;
00858 }
00859  
00860 
00861 // private slots
00862 
00863 // _file can also be a directory being currently held!
00864 void KDirListerCache::slotFileDirty( const QString& _file )
00865 {
00866   kdDebug(7004) << k_funcinfo << _file << endl;
00867 
00868   if ( !pendingUpdates[_file] )
00869   {
00870     KURL dir;
00871     dir.setPath( _file );
00872     if ( checkUpdate( dir.url(-1) ) )
00873       updateDirectory( dir );
00874 
00875     // the parent directory of _file
00876     dir.setPath( dir.directory() );
00877     if ( checkUpdate( dir.url() ) )
00878     {
00879       // Nice hack to save memory: use the qt object name to store the filename
00880       QTimer *timer = new QTimer( this, _file.utf8() );
00881       connect( timer, SIGNAL(timeout()), this, SLOT(slotFileDirtyDelayed()) );
00882       pendingUpdates.insert( _file, timer );
00883       timer->start( 500, true );
00884     }
00885   }
00886 }
00887 
00888 // delayed updating of files, FAM is flooding us with events
00889 void KDirListerCache::slotFileDirtyDelayed()
00890 {
00891   QString file = QString::fromUtf8( sender()->name() );
00892 
00893   kdDebug(7004) << k_funcinfo << file << endl;
00894 
00895   // TODO: do it better: don't always create/delete the QTimer but reuse it.
00896   // Delete the timer after the parent directory is removed from the cache.
00897   pendingUpdates.remove( file );
00898 
00899   KURL u;
00900   u.setPath( file );
00901   KFileItem *item = findByURL( 0, u ); // search all items
00902   if ( item )
00903   {
00904     // we need to refresh the item, because e.g. the permissions can have changed.
00905     aboutToRefreshItem( item );
00906     item->refresh();
00907     emitRefreshItem( item );
00908   }
00909 }
00910 
00911 void KDirListerCache::slotFileCreated( const QString& _file )
00912 {
00913   kdDebug(7004) << k_funcinfo << _file << endl;
00914   // XXX: how to avoid a complete rescan here?
00915   KURL u;
00916   u.setPath( _file );
00917   u.setPath( u.directory() );
00918   FilesAdded( u );
00919 }
00920 
00921 void KDirListerCache::slotFileDeleted( const QString& _file )
00922 {
00923   kdDebug(7004) << k_funcinfo << _file << endl;
00924   KURL u;
00925   u.setPath( _file );
00926   FilesRemoved( u );
00927 }
00928 
00929 void KDirListerCache::slotEntries( KIO::Job *job, const KIO::UDSEntryList &entries )
00930 {
00931   KURL url = joburl( static_cast<KIO::ListJob *>(job) );
00932   url.adjustPath(-1);
00933   QString urlStr = url.url();
00934 
00935   kdDebug(7004) << k_funcinfo << "new entries for " << url << endl;
00936 
00937   DirItem *dir = itemsInUse[urlStr];
00938   Q_ASSERT( dir );
00939 
00940   QPtrList<KDirLister> *listers = urlsCurrentlyListed[urlStr];
00941   Q_ASSERT( listers );
00942   Q_ASSERT( !listers->isEmpty() );
00943 
00944   // check if anyone wants the mimetypes immediately
00945   bool delayedMimeTypes = true;
00946   for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00947     delayedMimeTypes = delayedMimeTypes && kdl->d->delayedMimeTypes;
00948 
00949   // avoid creating these QStrings again and again
00950   static const QString& dot = KGlobal::staticQString(".");
00951   static const QString& dotdot = KGlobal::staticQString("..");
00952 
00953   KIO::UDSEntryListConstIterator it = entries.begin();
00954   KIO::UDSEntryListConstIterator end = entries.end();
00955 
00956   for ( ; it != end; ++it )
00957   {
00958     QString name;
00959 
00960     // find out about the name
00961     KIO::UDSEntry::ConstIterator entit = (*it).begin();
00962     for( ; entit != (*it).end(); ++entit )
00963       if ( (*entit).m_uds == KIO::UDS_NAME )
00964       {
00965         name = (*entit).m_str;
00966         break;
00967       }
00968 
00969     Q_ASSERT( !name.isEmpty() );
00970     if ( name.isEmpty() )
00971       continue;
00972 
00973     if ( name == dot )
00974     {
00975       Q_ASSERT( !dir->rootItem );
00976       dir->rootItem = new KFileItem( *it, url, delayedMimeTypes, true  );
00977 
00978       for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00979         if ( !kdl->d->rootFileItem && kdl->d->url == url )
00980           kdl->d->rootFileItem = dir->rootItem;
00981     }
00982     else if ( name != dotdot )
00983     {
00984       KFileItem* item = new KFileItem( *it, url, delayedMimeTypes, true );
00985       Q_ASSERT( item );
00986 
00987       //kdDebug(7004)<< "Adding item: " << item->url() << endl;
00988       dir->lstItems->append( item );
00989 
00990       for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00991         kdl->addNewItem( item );
00992     }
00993   }
00994 
00995   for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00996     kdl->emitItems();
00997 }
00998 
00999 void KDirListerCache::slotResult( KIO::Job *j )
01000 {
01001   Q_ASSERT( j );
01002   KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
01003   jobs.remove( job );
01004 
01005   KURL jobUrl = joburl( job );
01006   jobUrl.adjustPath(-1);  // need remove trailing slashes again, in case of redirections
01007   QString jobUrlStr = jobUrl.url();
01008 
01009   kdDebug(7004) << k_funcinfo << "finished listing " << jobUrl << endl;
01010 #ifdef DEBUG_CACHE
01011   printDebug();
01012 #endif
01013 
01014   QPtrList<KDirLister> *listers = urlsCurrentlyListed.take( jobUrlStr );
01015   Q_ASSERT( listers );
01016 
01017   // move the directory to the held directories, do it before emitting
01018   // the signals to make sure it exists in KDirListerCache in case someone
01019   // calls listDir during the signal emission
01020   Q_ASSERT( !urlsCurrentlyHeld[jobUrlStr] );
01021   urlsCurrentlyHeld.insert( jobUrlStr, listers );
01022 
01023   KDirLister *kdl;
01024 
01025   if ( job->error() )
01026   {
01027     for ( kdl = listers->first(); kdl; kdl = listers->next() )
01028     {
01029       kdl->jobDone( job );
01030       kdl->handleError( job );
01031       emit kdl->canceled( jobUrl );
01032       if ( kdl->numJobs() == 0 )
01033       {
01034         kdl->d->complete = true;
01035         emit kdl->canceled();
01036       }
01037     }
01038   }
01039   else
01040   {
01041     DirItem *dir = itemsInUse[jobUrlStr];
01042     Q_ASSERT( dir );
01043     dir->complete = true;
01044 
01045     for ( kdl = listers->first(); kdl; kdl = listers->next() )
01046     {
01047       kdl->jobDone( job );
01048       emit kdl->completed( jobUrl );
01049       if ( kdl->numJobs() == 0 )
01050       {
01051         kdl->d->complete = true;
01052         emit kdl->completed();
01053       }
01054     }
01055   }
01056 
01057   // TODO: hmm, if there was an error and job is a parent of one or more
01058   // of the pending urls we should cancel it/them as well
01059   processPendingUpdates();
01060 
01061 #ifdef DEBUG_CACHE
01062   printDebug();
01063 #endif
01064 }
01065 
01066 void KDirListerCache::slotRedirection( KIO::Job *j, const KURL& url )
01067 {
01068   Q_ASSERT( j );
01069   KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
01070 
01071   KURL oldUrl = job->url();  // here we really need the old url!
01072   KURL newUrl = url;
01073 
01074   // strip trailing slashes
01075   oldUrl.adjustPath(-1);
01076   newUrl.adjustPath(-1);
01077 
01078   if ( oldUrl == newUrl )
01079   {
01080     kdDebug(7004) << k_funcinfo << "New redirection url same as old, giving up." << endl;
01081     return;
01082   }
01083 
01084   kdDebug(7004) << k_funcinfo << oldUrl.prettyURL() << " -> " << newUrl.prettyURL() << endl;
01085 
01086 #ifdef DEBUG_CACHE
01087   printDebug();
01088 #endif
01089 
01090   // I don't think there can be dirItems that are childs of oldUrl.
01091   // Am I wrong here? And even if so, we don't need to delete them, right?
01092   // DF: redirection happens before listDir emits any item. Makes little sense otherwise.
01093 
01094   // oldUrl cannot be in itemsCached because only completed items are moved there
01095   DirItem *dir = itemsInUse.take( oldUrl.url() );
01096   Q_ASSERT( dir );
01097 
01098   QPtrList<KDirLister> *listers = urlsCurrentlyListed.take( oldUrl.url() );
01099   Q_ASSERT( listers );
01100   Q_ASSERT( !listers->isEmpty() );
01101 
01102   for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01103   {
01104     // TODO: put in own method?
01105     if ( kdl->d->url.equals( oldUrl, true ) )
01106     {
01107       kdl->d->rootFileItem = 0;
01108       kdl->d->url = newUrl;
01109     }
01110 
01111     *kdl->d->lstDirs.find( oldUrl ) = newUrl;
01112 
01113     if ( kdl->d->lstDirs.count() == 1 )
01114     {
01115       emit kdl->clear();
01116       emit kdl->redirection( newUrl );
01117       emit kdl->redirection( oldUrl, newUrl );
01118     }
01119     else
01120     {
01121       emit kdl->clear( oldUrl );
01122       emit kdl->redirection( oldUrl, newUrl );
01123     }
01124   }
01125 
01126   // when a lister was stopped before the job emits the redirection signal, the old url will
01127   // also be in urlsCurrentlyHeld
01128   QPtrList<KDirLister> *holders = urlsCurrentlyHeld.take( oldUrl.url() );
01129   if ( holders )
01130   {
01131     Q_ASSERT( !holders->isEmpty() );
01132 
01133     for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
01134     {
01135       kdl->jobStarted( job );
01136       
01137       // do it like when starting a new list-job that will redirect later
01138       emit kdl->started( oldUrl );
01139 
01140       // TODO: maybe don't emit started if there's an update running for newUrl already?
01141 
01142       if ( kdl->d->url.equals( oldUrl, true ) )
01143       {
01144         kdl->d->rootFileItem = 0;
01145         kdl->d->url = newUrl;
01146       }
01147 
01148       *kdl->d->lstDirs.find( oldUrl ) = newUrl;
01149 
01150       if ( kdl->d->lstDirs.count() == 1 )
01151       {
01152         emit kdl->clear();
01153         emit kdl->redirection( newUrl );
01154         emit kdl->redirection( oldUrl, newUrl );
01155       }
01156       else
01157       {
01158         emit kdl->clear( oldUrl );
01159         emit kdl->redirection( oldUrl, newUrl );
01160       }
01161     }
01162   }
01163 
01164   DirItem *newDir = itemsInUse[newUrl.url()];
01165   if ( newDir )
01166   {
01167     kdDebug(7004) << "slotRedirection: " << newUrl.url() << " already in use" << endl;
01168     
01169     // only in this case there can newUrl already be in urlsCurrentlyListed or urlsCurrentlyHeld
01170     delete dir;
01171 
01172     // get the job if one's running for newUrl already (can be a list-job or an update-job), but
01173     // do not return this 'job', which would happen because of the use of redirectionURL()
01174     KIO::ListJob *oldJob = jobForUrl( newUrl.url(), job );
01175 
01176     // listers of newUrl with oldJob: forget about the oldJob and use the already running one
01177     // which will be converted to an updateJob
01178     QPtrList<KDirLister> *curListers = urlsCurrentlyListed[newUrl.url()];
01179     if ( curListers )
01180     {
01181       kdDebug(7004) << "slotRedirection: and it is currently listed" << endl;
01182 
01183       Q_ASSERT( oldJob );  // ?!
01184 
01185       for ( KDirLister *kdl = curListers->first(); kdl; kdl = curListers->next() )  // listers of newUrl
01186       {
01187         kdl->jobDone( oldJob );
01188 
01189         kdl->jobStarted( job );
01190         kdl->connectJob( job );
01191       }
01192 
01193       // append listers of oldUrl with newJob to listers of newUrl with oldJob
01194       for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01195         curListers->append( kdl );
01196     }
01197     else
01198       urlsCurrentlyListed.insert( newUrl.url(), listers );
01199 
01200     if ( oldJob )         // kill the old job, be it a list-job or an update-job
01201       killJob( oldJob );
01202 
01203     // holders of newUrl: use the already running job which will be converted to an updateJob
01204     QPtrList<KDirLister> *curHolders = urlsCurrentlyHeld[newUrl.url()];
01205     if ( curHolders )
01206     {
01207       kdDebug(7004) << "slotRedirection: and it is currently held." << endl;
01208 
01209       for ( KDirLister *kdl = curHolders->first(); kdl; kdl = curHolders->next() )  // holders of newUrl
01210       {
01211         kdl->jobStarted( job );
01212         emit kdl->started( newUrl );
01213       }
01214 
01215       // append holders of oldUrl to holders of newUrl
01216       if ( holders )
01217         for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
01218           curHolders->append( kdl );
01219     }
01220     else if ( holders )
01221       urlsCurrentlyHeld.insert( newUrl.url(), holders );
01222 
01223     
01224     // emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed
01225     // TODO: make this a separate method?
01226     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01227     {
01228       if ( !kdl->d->rootFileItem && kdl->d->url == newUrl )
01229         kdl->d->rootFileItem = newDir->rootItem;
01230 
01231       kdl->addNewItems( *(newDir->lstItems) );
01232       kdl->emitItems();
01233     }
01234 
01235     if ( holders )
01236     {
01237       for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
01238       {
01239         if ( !kdl->d->rootFileItem && kdl->d->url == newUrl )
01240           kdl->d->rootFileItem = newDir->rootItem;
01241 
01242         kdl->addNewItems( *(newDir->lstItems) );
01243         kdl->emitItems();
01244       }
01245     }
01246   }
01247   else if ( (newDir = itemsCached.take( newUrl.url() )) )
01248   {
01249     kdDebug(7004) << "slotRedirection: " << newUrl.url() << " is unused, but already in the cache." << endl;
01250 
01251     delete dir;
01252     itemsInUse.insert( newUrl.url(), newDir );
01253     urlsCurrentlyListed.insert( newUrl.url(), listers );
01254     if ( holders )
01255       urlsCurrentlyHeld.insert( newUrl.url(), holders );
01256 
01257     // emit old items: listers, holders
01258     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01259     {
01260       if ( !kdl->d->rootFileItem && kdl->d->url == newUrl )
01261         kdl->d->rootFileItem = newDir->rootItem;
01262 
01263       kdl->addNewItems( *(newDir->lstItems) );
01264       kdl->emitItems();
01265     }
01266 
01267     if ( holders )
01268     {
01269       for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
01270       {
01271         if ( !kdl->d->rootFileItem && kdl->d->url == newUrl )
01272           kdl->d->rootFileItem = newDir->rootItem;
01273 
01274         kdl->addNewItems( *(newDir->lstItems) );
01275         kdl->emitItems();
01276       }
01277     }
01278   }
01279   else
01280   {
01281     kdDebug(7004) << "slotRedirection: " << newUrl.url() << " has not been listed yet." << endl;
01282 
01283     delete dir->rootItem;
01284     dir->rootItem = 0;
01285     dir->lstItems->clear();
01286     dir->redirect( newUrl );
01287     itemsInUse.insert( newUrl.url(), dir );
01288     url