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   if ( holders )
00452   {
00453     holders->removeRef( lister );
00454   }
00455 
00456   // remove the dir from lister->d->lstDirs so that it doesn't contain things
00457   // that itemsInUse doesn't. When emitting the canceled signals lstDirs must
00458   // not contain anything that itemsInUse does not contain. (otherwise it 
00459   // might crash in findByName()).
00460   lister->d->lstDirs.remove( lister->d->lstDirs.find( url ) );
00461 
00462   DirItem *item = itemsInUse[urlStr];
00463 
00464   if ( holders && holders->isEmpty() )
00465   {
00466     urlsCurrentlyHeld.remove( urlStr ); // this deletes the (empty) holders list
00467     if ( !urlsCurrentlyListed[urlStr] )
00468     {
00469       // item not in use anymore -> move into cache if complete
00470       itemsInUse.remove( urlStr );
00471 
00472       // this job is a running update
00473       KIO::ListJob *job = jobForUrl( urlStr );
00474       if ( job )
00475       {
00476         lister->jobDone( job );
00477         killJob( job );
00478         kdDebug(7004) << k_funcinfo << "Killing update job for " << urlStr << endl;
00479 
00480         emit lister->canceled( url );
00481         if ( lister->numJobs() == 0 )
00482         {
00483           lister->d->complete = true;
00484           emit lister->canceled();
00485         }
00486       }
00487 
00488       if ( notify )
00489         emit lister->clear( url );
00490 
00491       if ( item && item->complete )
00492       {
00493         kdDebug(7004) << k_funcinfo << lister << " item moved into cache: " << url << endl;
00494         itemsCached.insert( urlStr, item ); // TODO: may return false!!
00495 
00496         // Should we forget the dir for good, or keep a watch on it?
00497         // Generally keep a watch, except when it would prevent
00498         // unmounting a removable device (#37780)
00499         const bool isLocal = item->url.isLocalFile();
00500         const bool isManuallyMounted = isLocal && KIO::manually_mounted( item->url.path() );
00501         bool containsManuallyMounted = false;
00502         if ( !isManuallyMounted && item->lstItems && isLocal ) 
00503         {
00504           // Look for a manually-mounted directory inside
00505           // If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM
00506           // I hope this isn't too slow (manually_mounted caches the last device so most
00507           // of the time this is just a stat per subdir)
00508           KFileItemListIterator kit( *item->lstItems );
00509           for ( ; kit.current() && !containsManuallyMounted; ++kit )
00510             if ( (*kit)->isDir() && KIO::manually_mounted( (*kit)->url().path() ) )
00511               containsManuallyMounted = true;
00512         }
00513 
00514         if ( isManuallyMounted || containsManuallyMounted ) 
00515         {
00516           kdDebug(7004) << "Not adding a watch on " << item->url << " because it " <<
00517             ( isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir" ) << endl;
00518           item->complete = false; // set to "dirty"
00519         }
00520         else
00521           item->incAutoUpdate(); // keep watch
00522       }
00523       else
00524       {
00525         delete item;
00526         item = 0;
00527       }
00528     }
00529   }
00530 
00531   if ( item && lister->d->autoUpdate )
00532     item->decAutoUpdate();
00533 }
00534 
00535 void KDirListerCache::updateDirectory( const KURL& _dir )
00536 {
00537   kdDebug(7004) << k_funcinfo << _dir << endl;
00538 
00539   QString urlStr = _dir.url(-1);
00540   if ( !checkUpdate( urlStr ) )
00541     return;
00542 
00543   // A job can be running to
00544   //   - only list a new directory: the listers are in urlsCurrentlyListed
00545   //   - only update a directory: the listers are in urlsCurrentlyHeld
00546   //   - update a currently running listing: the listers are in urlsCurrentlyListed
00547   //     and urlsCurrentlyHeld
00548 
00549   QPtrList<KDirLister> *listers = urlsCurrentlyListed[urlStr];
00550   QPtrList<KDirLister> *holders = urlsCurrentlyHeld[urlStr];
00551 
00552   // restart the job for _dir if it is running already
00553   bool killed = false;
00554   QWidget *window = 0;
00555   KIO::ListJob *job = jobForUrl( urlStr );
00556   if ( job )
00557   {
00558      window = job->window();
00559 
00560      killJob( job );
00561      killed = true;
00562 
00563      if ( listers )
00564         for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00565            kdl->jobDone( job );
00566 
00567      if ( holders )
00568         for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
00569            kdl->jobDone( job );
00570   }
00571   kdDebug(7004) << k_funcinfo << "Killed = " << killed << endl;
00572 
00573   // we don't need to emit canceled signals since we only replaced the job,
00574   // the listing is continuing.
00575 
00576   Q_ASSERT( !listers || (listers && killed) );
00577 
00578   job = KIO::listDir( _dir, false /* no default GUI */ );
00579   jobs.insert( job, QValueList<KIO::UDSEntry>() );
00580 
00581   connect( job, SIGNAL(entries( KIO::Job *, const KIO::UDSEntryList & )),
00582            this, SLOT(slotUpdateEntries( KIO::Job *, const KIO::UDSEntryList & )) );
00583   connect( job, SIGNAL(result( KIO::Job * )),
00584            this, SLOT(slotUpdateResult( KIO::Job * )) );
00585 
00586   kdDebug(7004) << k_funcinfo << "update started in " << _dir << endl;
00587 
00588   if ( listers )
00589      for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00590         kdl->jobStarted( job );
00591 
00592   if ( holders )
00593   {
00594      if ( !killed )
00595      {
00596         bool first = true;
00597         for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
00598         {
00599            kdl->jobStarted( job );
00600            if ( first && kdl->d->window )
00601            {
00602               first = false;
00603               job->setWindow( kdl->d->window );
00604            }
00605            emit kdl->started( _dir );
00606         }
00607      }
00608      else
00609      {
00610         job->setWindow( window );
00611 
00612         for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
00613            kdl->jobStarted( job );
00614      }
00615   }
00616 }
00617 
00618 bool KDirListerCache::checkUpdate( const QString& _dir )
00619 {
00620   if ( !itemsInUse[_dir] )
00621   {
00622     DirItem *item = itemsCached[_dir];
00623     if ( item && item->complete )
00624     {
00625       item->complete = false;
00626       item->decAutoUpdate();
00627       // Hmm, this debug output might include login/password from the _dir URL.
00628       //kdDebug(7004) << k_funcinfo << "directory " << _dir << " not in use, marked dirty." << endl;
00629     }
00630     //else
00631       //kdDebug(7004) << k_funcinfo << "aborted, directory " << _dir << " not in cache." << endl;
00632 
00633     return false;
00634   }
00635   else
00636     return true;
00637 }
00638 
00639 KFileItemList *KDirListerCache::itemsForDir( const KURL &_dir ) const
00640 {
00641   QString urlStr = _dir.url(-1);
00642   DirItem *item = itemsInUse[ urlStr ];
00643   if ( !item )
00644     item = itemsCached[ urlStr ];
00645   return item ? item->lstItems : 0;
00646 }
00647 
00648 KFileItem *KDirListerCache::findByName( const KDirLister *lister, const QString& _name ) const
00649 {
00650   Q_ASSERT( lister );
00651 
00652   for ( KURL::List::Iterator it = lister->d->lstDirs.begin();
00653         it != lister->d->lstDirs.end(); ++it )
00654   {
00655     KFileItemListIterator kit( *itemsInUse[(*it).url()]->lstItems );
00656     for ( ; kit.current(); ++kit )
00657       if ( (*kit)->name() == _name )
00658         return (*kit);
00659   }
00660 
00661   return 0L;
00662 }
00663 
00664 KFileItem *KDirListerCache::findByURL( const KDirLister *lister, const KURL& _u ) const
00665 {
00666   KURL _url = _u;
00667   _url.adjustPath(-1);
00668 
00669   KURL parentDir( _url );
00670   parentDir.setPath( parentDir.directory() );
00671 
00672   // If lister is set, check that it contains this dir
00673   if ( lister && !lister->d->lstDirs.contains( parentDir ) )
00674       return 0L;
00675 
00676   KFileItemList *itemList = itemsForDir( parentDir );
00677   if ( itemList )
00678   {
00679     KFileItemListIterator kit( *itemList );
00680     for ( ; kit.current(); ++kit )
00681       if ( (*kit)->url() == _url )
00682         return (*kit);
00683   }
00684   return 0L;
00685 }
00686 
00687 void KDirListerCache::FilesAdded( const KURL &dir )
00688 {
00689   kdDebug(7004) << k_funcinfo << dir << endl;
00690   updateDirectory( dir );
00691 }
00692 
00693 void KDirListerCache::FilesRemoved( const KURL::List &fileList )
00694 {
00695   kdDebug(7004) << k_funcinfo << endl;
00696   KURL::List::ConstIterator it = fileList.begin();
00697   for ( ; it != fileList.end() ; ++it )
00698   {
00699     // emit the deleteItem signal if this file was shown in any view
00700     KFileItem *fileitem = 0L;
00701     KURL parentDir( *it );
00702     parentDir.setPath( parentDir.directory() );
00703     KFileItemList *lstItems = itemsForDir( parentDir );
00704     if ( lstItems )
00705     {
00706       KFileItem *fit = lstItems->first();
00707       for ( ; fit; fit = lstItems->next() )
00708         if ( fit->url() == *it ) {
00709           fileitem = fit;
00710           lstItems->take(); // remove fileitem from list
00711           break;
00712         }
00713     }
00714 
00715     // Tell the views about it before deleting the KFileItems. They might need the subdirs'
00716     // file items (see the dirtree).
00717     if ( fileitem )
00718     {
00719       QPtrList<KDirLister> *listers = urlsCurrentlyHeld[parentDir.url()];
00720       if ( listers )
00721         for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00722           kdl->emitDeleteItem( fileitem );
00723     }
00724 
00725     // If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case.
00726     if ( !fileitem || fileitem->isDir() )
00727     {
00728       // in case of a dir, check if we have any known children, there's much to do in that case
00729       // (stopping jobs, removing dirs from cache etc.)
00730       deleteDir( *it );
00731     }
00732 
00733     // now remove the item itself
00734     delete fileitem;
00735   }
00736 }
00737 
00738 void KDirListerCache::FilesChanged( const KURL::List &fileList )
00739 {
00740   KURL::List dirsToUpdate;
00741   kdDebug(7004) << k_funcinfo << "only half implemented" << endl;
00742   KURL::List::ConstIterator it = fileList.begin();
00743   for ( ; it != fileList.end() ; ++it )
00744   {
00745     if ( ( *it ).isLocalFile() )
00746     {
00747       kdDebug(7004) << "KDirListerCache::FilesChanged " << *it << endl;
00748       KFileItem *fileitem = findByURL( 0, *it );
00749       if ( fileitem )
00750       {
00751           // we need to refresh the item, because e.g. the permissions can have changed.
00752           aboutToRefreshItem( fileitem );
00753           fileitem->refresh();
00754           emitRefreshItem( fileitem );
00755       }
00756       else
00757           kdDebug(7004) << "item not found" << endl;
00758     } else {
00759       // For remote files, refresh() won't be able to figure out the new information.
00760       // Let's update the dir.
00761       KURL dir( *it );
00762       dir.setPath( dir.directory( true ) );
00763       if ( dirsToUpdate.find( dir ) == dirsToUpdate.end() )
00764         dirsToUpdate.prepend( dir );
00765     }
00766   }
00767 
00768   KURL::List::ConstIterator itdir = dirsToUpdate.begin();
00769   for ( ; itdir != dirsToUpdate.end() ; ++itdir )
00770     updateDirectory( *itdir );
00771   // ## TODO problems with current jobs listing/updating that dir
00772   // ( see kde-2.2.2's kdirlister )
00773 }
00774 
00775 void KDirListerCache::FileRenamed( const KURL &src, const KURL &dst )
00776 {
00777   kdDebug(7004) << k_funcinfo << src.prettyURL() << " -> " << dst.prettyURL() << endl;
00778 #ifdef DEBUG_CACHE
00779   printDebug();
00780 #endif
00781 
00782   // Somehow this should only be called if src is a dir. But how could we know if it is?
00783   // (Note that looking into itemsInUse isn't good enough. One could rename a subdir in a view.)
00784   renameDir( src, dst );
00785 
00786   // Now update the KFileItem representing that file or dir (not exclusive with the above!)
00787   KURL oldurl( src );
00788   oldurl.adjustPath( -1 );
00789   KFileItem *fileitem = findByURL( 0, oldurl );
00790   if ( fileitem )
00791   {
00792     if ( !fileitem->isLocalFile() && !fileitem->localPath().isEmpty() ) // it uses UDS_LOCAL_PATH? ouch, needs an update then
00793         FilesChanged( src );
00794     else
00795     {
00796         aboutToRefreshItem( fileitem );
00797         fileitem->setURL( dst );
00798         fileitem->refreshMimeType();
00799         emitRefreshItem( fileitem );
00800     }
00801   }
00802 #ifdef DEBUG_CACHE
00803   printDebug();
00804 #endif
00805 }
00806 
00807 void KDirListerCache::aboutToRefreshItem( KFileItem *fileitem )
00808 {
00809   // Look whether this item was shown in any view, i.e. held by any dirlister
00810   KURL parentDir( fileitem->url() );
00811   parentDir.setPath( parentDir.directory() );
00812   QString parentDirURL = parentDir.url();
00813   QPtrList<KDirLister> *listers = urlsCurrentlyHeld[parentDirURL];
00814   if ( listers )
00815     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00816       kdl->aboutToRefreshItem( fileitem );
00817 
00818   // Also look in urlsCurrentlyListed, in case the user manages to rename during a listing
00819   listers = urlsCurrentlyListed[parentDirURL];
00820   if ( listers )
00821     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00822       kdl->aboutToRefreshItem( fileitem );
00823 }
00824 
00825 void KDirListerCache::emitRefreshItem( KFileItem *fileitem )
00826 {
00827   // Look whether this item was shown in any view, i.e. held by any dirlister
00828   KURL parentDir( fileitem->url() );
00829   parentDir.setPath( parentDir.directory() );
00830   QString parentDirURL = parentDir.url();
00831   QPtrList<KDirLister> *listers = urlsCurrentlyHeld[parentDirURL];
00832   if ( listers )
00833     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00834     {
00835       kdl->addRefreshItem( fileitem );
00836       kdl->emitItems();
00837     }
00838 
00839   // Also look in urlsCurrentlyListed, in case the user manages to rename during a listing
00840   listers = urlsCurrentlyListed[parentDirURL];
00841   if ( listers )
00842     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00843     {
00844       kdl->addRefreshItem( fileitem );
00845       kdl->emitItems();
00846     }
00847 }
00848 
00849 KDirListerCache* KDirListerCache::self()
00850 {
00851   if ( !s_pSelf )
00852     s_pSelf = sd_KDirListerCache.setObject( s_pSelf, new KDirListerCache );
00853 
00854   return s_pSelf;
00855 }
00856 
00857 bool KDirListerCache::exists()
00858 {
00859   return s_pSelf != 0;
00860 }
00861  
00862 
00863 // private slots
00864 
00865 // _file can also be a directory being currently held!
00866 void KDirListerCache::slotFileDirty( const QString& _file )
00867 {
00868   kdDebug(7004) << k_funcinfo << _file << endl;
00869 
00870   if ( !pendingUpdates[_file] )
00871   {
00872     KURL dir;
00873     dir.setPath( _file );
00874     if ( checkUpdate( dir.url(-1) ) )
00875       updateDirectory( dir );
00876 
00877     // the parent directory of _file
00878     dir.setPath( dir.directory() );
00879     if ( checkUpdate( dir.url() ) )
00880     {
00881       // Nice hack to save memory: use the qt object name to store the filename
00882       QTimer *timer = new QTimer( this, _file.utf8() );
00883       connect( timer, SIGNAL(timeout()), this, SLOT(slotFileDirtyDelayed()) );
00884       pendingUpdates.insert( _file, timer );
00885       timer->start( 500, true );
00886     }
00887   }
00888 }
00889 
00890 // delayed updating of files, FAM is flooding us with events
00891 void KDirListerCache::slotFileDirtyDelayed()
00892 {
00893   QString file = QString::fromUtf8( sender()->name() );
00894 
00895   kdDebug(7004) << k_funcinfo << file << endl;
00896 
00897   // TODO: do it better: don't always create/delete the QTimer but reuse it.
00898   // Delete the timer after the parent directory is removed from the cache.
00899   pendingUpdates.remove( file );
00900 
00901   KURL u;
00902   u.setPath( file );
00903   KFileItem *item = findByURL( 0, u ); // search all items
00904   if ( item )
00905   {
00906     // we need to refresh the item, because e.g. the permissions can have changed.
00907     aboutToRefreshItem( item );
00908     item->refresh();
00909     emitRefreshItem( item );
00910   }
00911 }
00912 
00913 void KDirListerCache::slotFileCreated( const QString& _file )
00914 {
00915   kdDebug(7004) << k_funcinfo << _file << endl;
00916   // XXX: how to avoid a complete rescan here?
00917   KURL u;
00918   u.setPath( _file );
00919   u.setPath( u.directory() );
00920   FilesAdded( u );
00921 }
00922 
00923 void KDirListerCache::slotFileDeleted( const QString& _file )
00924 {
00925   kdDebug(7004) << k_funcinfo << _file << endl;
00926   KURL u;
00927   u.setPath( _file );
00928   FilesRemoved( u );
00929 }
00930 
00931 void KDirListerCache::slotEntries( KIO::Job *job, const KIO::UDSEntryList &entries )
00932 {
00933   KURL url = joburl( static_cast<KIO::ListJob *>(job) );
00934   url.adjustPath(-1);
00935   QString urlStr = url.url();
00936 
00937   kdDebug(7004) << k_funcinfo << "new entries for " << url << endl;
00938 
00939   DirItem *dir = itemsInUse[urlStr];
00940   Q_ASSERT( dir );
00941 
00942   QPtrList<KDirLister> *listers = urlsCurrentlyListed[urlStr];
00943   Q_ASSERT( listers );
00944   Q_ASSERT( !listers->isEmpty() );
00945 
00946   // check if anyone wants the mimetypes immediately
00947   bool delayedMimeTypes = true;
00948   for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00949     delayedMimeTypes = delayedMimeTypes && kdl->d->delayedMimeTypes;
00950 
00951   // avoid creating these QStrings again and again
00952   static const QString& dot = KGlobal::staticQString(".");
00953   static const QString& dotdot = KGlobal::staticQString("..");
00954 
00955   KIO::UDSEntryListConstIterator it = entries.begin();
00956   KIO::UDSEntryListConstIterator end = entries.end();
00957 
00958   for ( ; it != end; ++it )
00959   {
00960     QString name;
00961 
00962     // find out about the name
00963     KIO::UDSEntry::ConstIterator entit = (*it).begin();
00964     for( ; entit != (*it).end(); ++entit )
00965       if ( (*entit).m_uds == KIO::UDS_NAME )
00966       {
00967         name = (*entit).m_str;
00968         break;
00969       }
00970 
00971     Q_ASSERT( !name.isEmpty() );
00972     if ( name.isEmpty() )
00973       continue;
00974 
00975     if ( name == dot )
00976     {
00977       Q_ASSERT( !dir->rootItem );
00978       dir->rootItem = new KFileItem( *it, url, delayedMimeTypes, true  );
00979 
00980       for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00981         if ( !kdl->d->rootFileItem && kdl->d->url == url )
00982           kdl->d->rootFileItem = dir->rootItem;
00983     }
00984     else if ( name != dotdot )
00985     {
00986       KFileItem* item = new KFileItem( *it, url, delayedMimeTypes, true );
00987       Q_ASSERT( item );
00988 
00989       //kdDebug(7004)<< "Adding item: " << item->url() << endl;
00990       dir->lstItems->append( item );
00991 
00992       for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00993         kdl->addNewItem( item );
00994     }
00995   }
00996 
00997   for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
00998     kdl->emitItems();
00999 }
01000 
01001 void KDirListerCache::slotResult( KIO::Job *j )
01002 {
01003   Q_ASSERT( j );
01004   KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
01005   jobs.remove( job );
01006 
01007   KURL jobUrl = joburl( job );
01008   jobUrl.adjustPath(-1);  // need remove trailing slashes again, in case of redirections
01009   QString jobUrlStr = jobUrl.url();
01010 
01011   kdDebug(7004) << k_funcinfo << "finished listing " << jobUrl << endl;
01012 #ifdef DEBUG_CACHE
01013   printDebug();
01014 #endif
01015 
01016   QPtrList<KDirLister> *listers = urlsCurrentlyListed.take( jobUrlStr );
01017   Q_ASSERT( listers );
01018 
01019   // move the directory to the held directories, do it before emitting
01020   // the signals to make sure it exists in KDirListerCache in case someone
01021   // calls listDir during the signal emission
01022   Q_ASSERT( !urlsCurrentlyHeld[jobUrlStr] );
01023   urlsCurrentlyHeld.insert( jobUrlStr, listers );
01024 
01025   KDirLister *kdl;
01026 
01027   if ( job->error() )
01028   {
01029     for ( kdl = listers->first(); kdl; kdl = listers->next() )
01030     {
01031       kdl->jobDone( job );
01032       kdl->handleError( job );
01033       emit kdl->canceled( jobUrl );
01034       if ( kdl->numJobs() == 0 )
01035       {
01036         kdl->d->complete = true;
01037         emit kdl->canceled();
01038       }
01039     }
01040   }
01041   else
01042   {
01043     DirItem *dir = itemsInUse[jobUrlStr];
01044     Q_ASSERT( dir );
01045     dir->complete = true;
01046 
01047     for ( kdl = listers->first(); kdl; kdl = listers->next() )
01048     {
01049       kdl->jobDone( job );
01050       emit kdl->completed( jobUrl );
01051       if ( kdl->numJobs() == 0 )
01052       {
01053         kdl->d->complete = true;
01054         emit kdl->completed();
01055       }
01056     }
01057   }
01058 
01059   // TODO: hmm, if there was an error and job is a parent of one or more
01060   // of the pending urls we should cancel it/them as well
01061   processPendingUpdates();
01062 
01063 #ifdef DEBUG_CACHE
01064   printDebug();
01065 #endif
01066 }
01067 
01068 void KDirListerCache::slotRedirection( KIO::Job *j, const KURL& url )
01069 {
01070   Q_ASSERT( j );
01071   KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
01072 
01073   KURL oldUrl = job->url();  // here we really need the old url!
01074   KURL newUrl = url;
01075 
01076   // strip trailing slashes
01077   oldUrl.adjustPath(-1);
01078   newUrl.adjustPath(-1);
01079 
01080   if ( oldUrl == newUrl )
01081   {
01082     kdDebug(7004) << k_funcinfo << "New redirection url same as old, giving up." << endl;
01083     return;
01084   }
01085 
01086   kdDebug(7004) << k_funcinfo << oldUrl.prettyURL() << " -> " << newUrl.prettyURL() << endl;
01087 
01088 #ifdef DEBUG_CACHE
01089   printDebug();
01090 #endif
01091 
01092   // I don't think there can be dirItems that are childs of oldUrl.
01093   // Am I wrong here? And even if so, we don't need to delete them, right?
01094   // DF: redirection happens before listDir emits any item. Makes little sense otherwise.
01095 
01096   // oldUrl cannot be in itemsCached because only completed items are moved there
01097   DirItem *dir = itemsInUse.take( oldUrl.url() );
01098   Q_ASSERT( dir );
01099 
01100   QPtrList<KDirLister> *listers = urlsCurrentlyListed.take( oldUrl.url() );
01101   Q_ASSERT( listers );
01102   Q_ASSERT( !listers->isEmpty() );
01103 
01104   for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01105   {
01106     // TODO: put in own method?
01107     if ( kdl->d->url.equals( oldUrl, true ) )
01108     {
01109       kdl->d->rootFileItem = 0;
01110       kdl->d->url = newUrl;
01111     }
01112 
01113     *kdl->d->lstDirs.find( oldUrl ) = newUrl;
01114 
01115     if ( kdl->d->lstDirs.count() == 1 )
01116     {
01117       emit kdl->clear();
01118       emit kdl->redirection( newUrl );
01119       emit kdl->redirection( oldUrl, newUrl );
01120     }
01121     else
01122     {
01123       emit kdl->clear( oldUrl );
01124       emit kdl->redirection( oldUrl, newUrl );
01125     }
01126   }
01127 
01128   // when a lister was stopped before the job emits the redirection signal, the old url will
01129   // also be in urlsCurrentlyHeld
01130   QPtrList<KDirLister> *holders = urlsCurrentlyHeld.take( oldUrl.url() );
01131   if ( holders )
01132   {
01133     Q_ASSERT( !holders->isEmpty() );
01134 
01135     for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
01136     {
01137       kdl->jobStarted( job );
01138       
01139       // do it like when starting a new list-job that will redirect later
01140       emit kdl->started( oldUrl );
01141 
01142       // TODO: maybe don't emit started if there's an update running for newUrl already?
01143 
01144       if ( kdl->d->url.equals( oldUrl, true ) )
01145       {
01146         kdl->d->rootFileItem = 0;
01147         kdl->d->url = newUrl;
01148       }
01149 
01150       *kdl->d->lstDirs.find( oldUrl ) = newUrl;
01151 
01152       if ( kdl->d->lstDirs.count() == 1 )
01153       {
01154         emit kdl->clear();
01155         emit kdl->redirection( newUrl );
01156         emit kdl->redirection( oldUrl, newUrl );
01157       }
01158       else
01159       {
01160         emit kdl->clear( oldUrl );
01161         emit kdl->redirection( oldUrl, newUrl );
01162       }
01163     }
01164   }
01165 
01166   DirItem *newDir = itemsInUse[newUrl.url()];
01167   if ( newDir )
01168   {
01169     kdDebug(7004) << "slotRedirection: " << newUrl.url() << " already in use" << endl;
01170     
01171     // only in this case there can newUrl already be in urlsCurrentlyListed or urlsCurrentlyHeld
01172     delete dir;
01173 
01174     // get the job if one's running for newUrl already (can be a list-job or an update-job), but
01175     // do not return this 'job', which would happen because of the use of redirectionURL()
01176     KIO::ListJob *oldJob = jobForUrl( newUrl.url(), job );
01177 
01178     // listers of newUrl with oldJob: forget about the oldJob and use the already running one
01179     // which will be converted to an updateJob
01180     QPtrList<KDirLister> *curListers = urlsCurrentlyListed[newUrl.url()];
01181     if ( curListers )
01182     {
01183       kdDebug(7004) << "slotRedirection: and it is currently listed" << endl;
01184 
01185       Q_ASSERT( oldJob );  // ?!
01186 
01187       for ( KDirLister *kdl = curListers->first(); kdl; kdl = curListers->next() )  // listers of newUrl
01188       {
01189         kdl->jobDone( oldJob );
01190 
01191         kdl->jobStarted( job );
01192         kdl->connectJob( job );
01193       }
01194 
01195       // append listers of oldUrl with newJob to listers of newUrl with oldJob
01196       for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01197         curListers->append( kdl );
01198     }
01199     else
01200       urlsCurrentlyListed.insert( newUrl.url(), listers );
01201 
01202     if ( oldJob )         // kill the old job, be it a list-job or an update-job
01203       killJob( oldJob );
01204 
01205     // holders of newUrl: use the already running job which will be converted to an updateJob
01206     QPtrList<KDirLister> *curHolders = urlsCurrentlyHeld[newUrl.url()];
01207     if ( curHolders )
01208     {
01209       kdDebug(7004) << "slotRedirection: and it is currently held." << endl;
01210 
01211       for ( KDirLister *kdl = curHolders->first(); kdl; kdl = curHolders->next() )  // holders of newUrl
01212       {
01213         kdl->jobStarted( job );
01214         emit kdl->started( newUrl );
01215       }
01216 
01217       // append holders of oldUrl to holders of newUrl
01218       if ( holders )
01219         for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
01220           curHolders->append( kdl );
01221     }
01222     else if ( holders )
01223       urlsCurrentlyHeld.insert( newUrl.url(), holders );
01224 
01225     
01226     // emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed
01227     // TODO: make this a separate method?
01228     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01229     {
01230       if ( !kdl->d->rootFileItem && kdl->d->url == newUrl )
01231         kdl->d->rootFileItem = newDir->rootItem;
01232 
01233       kdl->addNewItems( *(newDir->lstItems) );
01234       kdl->emitItems();
01235     }
01236 
01237     if ( holders )
01238     {
01239       for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
01240       {
01241         if ( !kdl->d->rootFileItem && kdl->d->url == newUrl )
01242           kdl->d->rootFileItem = newDir->rootItem;
01243 
01244         kdl->addNewItems( *(newDir->lstItems) );
01245         kdl->emitItems();
01246       }
01247     }
01248   }
01249   else if ( (newDir = itemsCached.take( newUrl.url() )) )
01250   {
01251     kdDebug(7004) << "slotRedirection: " << newUrl.url() << " is unused, but already in the cache." << endl;
01252 
01253     delete dir;
01254     itemsInUse.insert( newUrl.url(), newDir );
01255     urlsCurrentlyListed.insert( newUrl.url(), listers );
01256     if ( holders )
01257       urlsCurrentlyHeld.insert( newUrl.url(), holders );
01258 
01259     // emit old items: listers, holders
01260     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01261     {
01262       if ( !kdl->d->rootFileItem && kdl->d->url == newUrl )
01263         kdl->d->rootFileItem = newDir->rootItem;
01264 
01265       kdl->addNewItems( *(newDir->lstItems) );
01266       kdl->emitItems();
01267     }
01268 
01269     if ( holders )
01270     {
01271       for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
01272       {
01273         if ( !kdl->d->rootFileItem && kdl->d->url == newUrl )
01274           kdl->d->rootFileItem = newDir->rootItem;
01275 
01276         kdl->addNewItems( *(newDir->lstItems) );
01277         kdl->emitItems();
01278       }
01279     }
01280   }
01281   else
01282   {
01283     kdDebug(7004) << "slotRedirection: " << newUrl.url() << " has not been listed yet." << endl;
01284 
01285     delete dir->rootItem;
01286     dir->rootItem = 0;
01287     dir->lstItems->clear();
01288     dir->redirect( newUrl );
01289     itemsInUse.insert( newUrl.url(), dir );
01290     urlsCurrentlyListed.insert( newUrl.url(), listers );
01291 
01292     if ( holders )
01293       urlsCurrentlyHeld.insert( newUrl.url(), holders );
01294     else
01295     {
01296 #ifdef DEBUG_CACHE
01297       printDebug();
01298 #endif
01299       return; // only in this case the job doesn't need to be converted, 
01300     }
01301   }
01302 
01303   // make the job an update job
01304   job->disconnect( this );
01305     
01306   connect( job, SIGNAL(entries( KIO::Job *, const KIO::UDSEntryList & )),
01307            this, SLOT(slotUpdateEntries( KIO::Job *, const KIO::UDSEntryList & )) );
01308   connect( job, SIGNAL(result( KIO::Job * )),
01309            this, SLOT(slotUpdateResult( KIO::Job * )) );
01310 
01311   // FIXME: autoUpdate-Counts!!
01312 
01313 #ifdef DEBUG_CACHE
01314   printDebug();
01315 #endif
01316 }
01317 
01318 void KDirListerCache::renameDir( const KURL &oldUrl, const KURL &newUrl )
01319 {
01320   kdDebug(7004) << k_funcinfo << oldUrl.prettyURL() << " -> " << newUrl.prettyURL() << endl;
01321   QString oldUrlStr = oldUrl.url(-1);
01322   QString newUrlStr = newUrl.url(-1);
01323 
01324   // Not enough. Also need to look at any child dir, even sub-sub-sub-dir.
01325   //DirItem *dir = itemsInUse.take( oldUrlStr );
01326   //emitRedirections( oldUrl, url );
01327 
01328   // Look at all dirs being listed/shown
01329   QDictIterator<DirItem> itu( itemsInUse );
01330   bool goNext;
01331   while ( itu.current() )
01332   {
01333     goNext = true;
01334     DirItem *dir = itu.current();
01335     KURL oldDirUrl ( itu.currentKey() );
01336     //kdDebug(7004) << "itemInUse: " << oldDirUrl.prettyURL() << endl;
01337     // Check if this dir is oldUrl, or a subfolder of it
01338     if ( oldUrl.isParentOf( oldDirUrl ) )
01339     {
01340       // TODO should use KURL::cleanpath like isParentOf does
01341       QString relPath = oldDirUrl.path().mid( oldUrl.path().length() );
01342 
01343       KURL newDirUrl( newUrl ); // take new base
01344       if ( !relPath.isEmpty() )
01345         newDirUrl.addPath( relPath ); // add unchanged relative path
01346       //kdDebug(7004) << "KDirListerCache::renameDir new url=" << newDirUrl.prettyURL() << endl;
01347 
01348       // Update URL in dir item and in itemsInUse
01349       dir->redirect( newDirUrl );
01350       itemsInUse.remove( itu.currentKey() ); // implies ++itu
01351       itemsInUse.insert( newDirUrl.url(-1), dir );
01352       goNext = false; // because of the implied ++itu above
01353       if ( dir->lstItems )
01354       {
01355         // Rename all items under that dir
01356         KFileItemListIterator kit( *dir->lstItems );
01357         for ( ; kit.current(); ++kit )
01358         {
01359           KURL oldItemUrl = (*kit)->url();
01360           QString oldItemUrlStr( oldItemUrl.url(-1) );
01361           KURL newItemUrl( oldItemUrl );
01362           newItemUrl.setPath( newDirUrl.path() );
01363           newItemUrl.addPath( oldItemUrl.fileName() );
01364           kdDebug(7004) << "KDirListerCache::renameDir renaming " << oldItemUrlStr << " to " << newItemUrl.url() << endl;
01365           (*kit)->setURL( newItemUrl );
01366         }
01367       }
01368       emitRedirections( oldDirUrl, newDirUrl );
01369     }
01370     if ( goNext )
01371       ++itu;
01372   }
01373 
01374   // Is oldUrl a directory in the cache?
01375   // Remove any child of oldUrl from the cache - even if the renamed dir itself isn't in it!
01376   removeDirFromCache( oldUrl );
01377   // TODO rename, instead.
01378 }
01379 
01380 void KDirListerCache::emitRedirections( const KURL &oldUrl, const KURL &url )
01381 {
01382   kdDebug(7004) << k_funcinfo << oldUrl.prettyURL() << " -> " << url.prettyURL() << endl;
01383   QString oldUrlStr = oldUrl.url(-1);
01384   QString urlStr = url.url(-1);
01385 
01386   KIO::ListJob *job = jobForUrl( oldUrlStr );
01387   if ( job )
01388     killJob( job );
01389 
01390   // Check if we were listing this dir. Need to abort and restart with new name in that case.
01391   QPtrList<KDirLister> *listers = urlsCurrentlyListed.take( oldUrlStr );
01392   if ( listers )
01393   {
01394     // Tell the world that the job listing the old url is dead.
01395     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01396     {
01397       if ( job )
01398         kdl->jobDone( job );
01399 
01400       emit kdl->canceled( oldUrl );
01401     }
01402 
01403     urlsCurrentlyListed.insert( urlStr, listers );
01404   }
01405 
01406   // Check if we are currently displaying this directory (odds opposite wrt above)
01407   // Update urlsCurrentlyHeld dict with new URL
01408   QPtrList<KDirLister> *holders = urlsCurrentlyHeld.take( oldUrlStr );
01409   if ( holders )
01410   {
01411     if ( job )
01412       for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
01413         kdl->jobDone( job );
01414 
01415     urlsCurrentlyHeld.insert( urlStr, holders );
01416   }
01417 
01418   if ( listers )
01419   {
01420     updateDirectory( url );
01421 
01422     // Tell the world about the new url
01423     for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01424       emit kdl->started( url );
01425   }
01426 
01427   if ( holders )
01428   {
01429     // And notify the dirlisters of the redirection
01430     for ( KDirLister *kdl = holders->first(); kdl; kdl = holders->next() )
01431     {
01432       *kdl->d->lstDirs.find( oldUrl ) = url;
01433 
01434       if ( kdl->d->lstDirs.count() == 1 )
01435         emit kdl->redirection( url );
01436 
01437       emit kdl->redirection( oldUrl, url );
01438     }
01439   }
01440 }
01441 
01442 void KDirListerCache::removeDirFromCache( const KURL& dir )
01443 {
01444   kdDebug(7004) << "KDirListerCache::removeDirFromCache " << dir.prettyURL() << endl;
01445   QCacheIterator<DirItem> itc( itemsCached );
01446   while ( itc.current() )
01447   {
01448     if ( dir.isParentOf( KURL( itc.currentKey() ) ) )
01449       itemsCached.remove( itc.currentKey() );
01450     else
01451       ++itc;
01452   }
01453 }
01454 
01455 void KDirListerCache::slotUpdateEntries( KIO::Job* job, const KIO::UDSEntryList& list )
01456 {
01457   jobs[static_cast<KIO::ListJob*>(job)] += list;
01458 }
01459 
01460 void KDirListerCache::slotUpdateResult( KIO::Job * j )
01461 {
01462   Q_ASSERT( j );
01463   KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
01464 
01465   KURL jobUrl = joburl( job );
01466   jobUrl.adjustPath(-1);  // need remove trailing slashes again, in case of redirections
01467   QString jobUrlStr = jobUrl.url();
01468 
01469   kdDebug(7004) << k_funcinfo << "finished update " << jobUrl << endl;
01470 
01471   KDirLister *kdl;
01472 
01473   QPtrList<KDirLister> *listers = urlsCurrentlyHeld[jobUrlStr];
01474   QPtrList<KDirLister> *tmpLst = urlsCurrentlyListed.take( jobUrlStr );
01475 
01476   if ( tmpLst )
01477   {
01478     if ( listers )
01479       for ( kdl = tmpLst->first(); kdl; kdl = tmpLst->next() )
01480       {
01481         Q_ASSERT( listers->containsRef( kdl ) == 0 );
01482         listers->append( kdl );
01483       }
01484     else
01485     {
01486       listers = tmpLst;
01487       urlsCurrentlyHeld.insert( jobUrlStr, listers );
01488     }
01489   }
01490 
01491   // once we are updating dirs that are only in the cache this will fail!
01492   Q_ASSERT( listers );
01493 
01494   if ( job->error() )
01495   {
01496     for ( kdl = listers->first(); kdl; kdl = listers->next() )
01497     {
01498       kdl->jobDone( job );
01499 
01500       //don't bother the user
01501       //kdl->handleError( job );
01502 
01503       emit kdl->canceled( jobUrl );
01504       if ( kdl->numJobs() == 0 )
01505       {
01506         kdl->d->complete = true;
01507         emit kdl->canceled();
01508       }
01509     }
01510 
01511     jobs.remove( job );
01512 
01513     // TODO: if job is a parent of one or more
01514     // of the pending urls we should cancel them
01515     processPendingUpdates();
01516     return;
01517   }
01518 
01519   DirItem *dir = itemsInUse[jobUrlStr];
01520   dir->complete = true;
01521 
01522 
01523   // check if anyone wants the mimetypes immediately
01524   bool delayedMimeTypes = true;
01525   for ( kdl = listers->first(); kdl; kdl = listers->next() )
01526     delayedMimeTypes = delayedMimeTypes && kdl->d->delayedMimeTypes;
01527 
01528   // should be enough to get reasonable speed in most cases
01529   QDict<KFileItem> fileItems( 9973 );
01530 
01531   KFileItemListIterator kit ( *(dir->lstItems) );
01532 
01533   // Unmark all items in url
01534   for ( ; kit.current(); ++kit )
01535   {
01536     (*kit)->unmark();
01537     fileItems.insert( (*kit)->url().url(), *kit );
01538   }
01539 
01540   static const QString& dot = KGlobal::staticQString(".");
01541   static const QString& dotdot = KGlobal::staticQString("..");
01542 
01543   KFileItem *item = 0, *tmp;
01544 
01545   QValueList<KIO::UDSEntry> buf = jobs[job];
01546   QValueListIterator<KIO::UDSEntry> it = buf.begin();
01547   for ( ; it != buf.end(); ++it )
01548   {
01549     // Form the complete url
01550     if ( !item )
01551       item = new KFileItem( *it, jobUrl, delayedMimeTypes, true );
01552     else
01553       item->setUDSEntry( *it, jobUrl, delayedMimeTypes, true );
01554 
01555     // Find out about the name
01556     QString name = item->name();
01557     Q_ASSERT( !name.isEmpty() );
01558 
01559     // we duplicate the check for dotdot here, to avoid iterating over
01560     // all items again and checking in matchesFilter() that way.
01561     if ( name.isEmpty() || name == dotdot )
01562       continue;
01563 
01564     if ( name == dot )
01565     {
01566       // if the update was started before finishing the original listing
01567       // there is no root item yet
01568       if ( !dir->rootItem )
01569       {
01570         dir->rootItem = item;
01571         item = 0;
01572 
01573         for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01574           if ( !kdl->d->rootFileItem && kdl->d->url == jobUrl )
01575             kdl->d->rootFileItem = dir->rootItem;
01576       }
01577 
01578       continue;
01579     }
01580 
01581     // Find this item
01582     if ( (tmp = fileItems[item->url().url()]) )
01583     {
01584       tmp->mark();
01585 
01586       // check if something changed for this file
01587       if ( !tmp->cmp( *item ) )
01588       {
01589         for ( kdl = listers->first(); kdl; kdl = listers->next() )
01590           kdl->aboutToRefreshItem( tmp );
01591 
01592         //kdDebug(7004) << "slotUpdateResult: file changed: " << tmp->name() << endl;
01593         tmp->assign( *item );
01594 
01595         for ( kdl = listers->first(); kdl; kdl = listers->next() )
01596           kdl->addRefreshItem( tmp );
01597       }
01598     }
01599     else // this is a new file
01600     {
01601       //kdDebug(7004) << "slotUpdateResult: new file: " << name << endl;
01602 
01603       item->mark();
01604       dir->lstItems->append( item );
01605 
01606       for ( kdl = listers->first(); kdl; kdl = listers->next() )
01607         kdl->addNewItem( item );
01608 
01609       // item used, we need a new one for the next iteration
01610       item = 0;
01611     }
01612   }
01613 
01614   if ( item )
01615     delete item;
01616 
01617   jobs.remove( job );
01618 
01619   deleteUnmarkedItems( listers, dir->lstItems );
01620 
01621   for ( kdl = listers->first(); kdl; kdl = listers->next() )
01622   {
01623     kdl->emitItems();
01624 
01625     kdl->jobDone( job );
01626 
01627     emit kdl->completed( jobUrl );
01628     if ( kdl->numJobs() == 0 )
01629     {
01630       kdl->d->complete = true;
01631       emit kdl->completed();
01632     }
01633   }
01634 
01635   // TODO: hmm, if there was an error and job is a parent of one or more
01636   // of the pending urls we should cancel it/them as well
01637   processPendingUpdates();
01638 }
01639 
01640 // private
01641 
01642 KIO::ListJob *KDirListerCache::jobForUrl( const QString& url, KIO::ListJob *not_job )
01643 {
01644   KIO::ListJob *job;
01645   QMap< KIO::ListJob *, QValueList<KIO::UDSEntry> >::Iterator it = jobs.begin();
01646   while ( it != jobs.end() )
01647   {
01648     job = it.key();
01649     if ( joburl( job ).url(-1) == url && job != not_job )
01650        return job;
01651     ++it;
01652   }
01653   return 0;
01654 }
01655 
01656 const KURL& KDirListerCache::joburl( KIO::ListJob *job )
01657 {
01658   if ( job->redirectionURL().isValid() )
01659      return job->redirectionURL();
01660   else
01661      return job->url();
01662 }
01663 
01664 void KDirListerCache::killJob( KIO::ListJob *job )
01665 {
01666   jobs.remove( job );
01667   job->disconnect( this );
01668   job->kill();
01669 }
01670 
01671 void KDirListerCache::deleteUnmarkedItems( QPtrList<KDirLister> *listers, KFileItemList *lstItems )
01672 {
01673   // Find all unmarked items and delete them
01674   KFileItem* item;
01675   lstItems->first();
01676   while ( (item = lstItems->current()) )
01677     if ( !item->isMarked() )
01678     {
01679       //kdDebug() << k_funcinfo << item->name() << endl;
01680       for ( KDirLister *kdl = listers->first(); kdl; kdl = listers->next() )
01681         kdl->emitDeleteItem( item );
01682 
01683       if ( item->isDir() )
01684         deleteDir( item->url() );
01685 
01686       // finally actually delete the item
01687       lstItems->take();
01688       delete item;
01689     }
01690     else
01691       lstItems->next();
01692 }
01693 
01694 void KDirListerCache::deleteDir( const KURL& dirUrl )
01695 {
01696   //kdDebug() << k_funcinfo << dirUrl.prettyURL() << endl;
01697   // unregister and remove the childs of the deleted item.
01698   // Idea: tell all the KDirListers that they should forget the dir
01699   //       and then remove it from the cache.
01700 
01701   QDictIterator<DirItem> itu( itemsInUse );
01702   while ( itu.current() )
01703   {
01704     KURL deletedUrl( itu.currentKey() );
01705     if ( dirUrl.isParentOf( deletedUrl ) )
01706     {
01707       // stop all jobs for deletedUrl
01708 
01709       QPtrList<KDirLister> *kdls = urlsCurrentlyListed[deletedUrl.url()];
01710       if ( kdls )  // yeah, I lack good names
01711       {
01712         // we need a copy because stop modifies the list
01713         kdls = new QPtrList<KDirLister>( *kdls );
01714         for ( KDirLister *kdl = kdls->first(); kdl; kdl = kdls->next() )
01715           stop( kdl, deletedUrl );
01716 
01717         delete kdls;
01718       }
01719 
01720       // tell listers holding deletedUrl to forget about it
01721       // this will stop running updates for deletedUrl as well
01722 
01723       kdls = urlsCurrentlyHeld[deletedUrl.url()];
01724       if ( kdls )
01725       {
01726         // we need a copy because forgetDirs modifies the list
01727         kdls = new QPtrList<KDirLister>( *kdls );
01728 
01729         for ( KDirLister *kdl = kdls->first(); kdl; kdl = kdls->next() )
01730         {
01731           // lister's root is the deleted item
01732           if ( kdl->d->url == deletedUrl )
01733           {
01734             // tell the view first. It might need the subdirs' items (which forgetDirs will delete)
01735             if ( kdl->d->rootFileItem )
01736               emit kdl->deleteItem( kdl->d->rootFileItem );
01737             forgetDirs( kdl );
01738             kdl->d->rootFileItem = 0;
01739           }
01740           else
01741           {
01742             bool treeview = kdl->d->lstDirs.count() > 1;
01743             if ( !treeview )
01744               emit kdl->clear();
01745 
01746             forgetDirs( kdl, deletedUrl, treeview );
01747           }
01748         }
01749 
01750         delete kdls;
01751       }
01752 
01753       // delete the entry for deletedUrl - should not be needed, it's in
01754       // items cached now
01755 
01756       DirItem *dir = itemsInUse.take( deletedUrl.url() );
01757       Q_ASSERT( !dir );
01758       if ( !dir ) // take didn't find it - move on
01759           ++itu;
01760     }
01761     else
01762       ++itu;
01763   }
01764 
01765   // remove the children from the cache
01766   removeDirFromCache( dirUrl );
01767 }
01768 
01769 void KDirListerCache::processPendingUpdates()
01770 {
01771   // TODO
01772 }
01773 
01774 #ifndef NDEBUG
01775 void KDirListerCache::printDebug()
01776 {
01777   kdDebug(7004) << "Items in use: " << endl;
01778   QDictIterator<DirItem> itu( itemsInUse );
01779   for ( ; itu.current() ; ++itu ) {
01780       kdDebug(7004) << "   " << itu.currentKey() << "  URL: " << itu.current()->url
01781                     << " rootItem: " << ( itu.current()->rootItem ? itu.current()->rootItem->url() : KURL() )
01782                     << " autoUpdates refcount: " << itu.current()->autoUpdates
01783                     << " complete: " << itu.current()->complete
01784                   << ( itu.current()->lstItems ? QString(" with %1 items.").arg(itu.current()->lstItems->count()) : QString(" lstItems=NULL") ) << endl;
01785   }
01786 
01787   kdDebug(7004) << "urlsCurrentlyHeld: " << endl;
01788   QDictIterator< QPtrList<KDirLister> > it( urlsCurrentlyHeld );
01789   for ( ; it.current() ; ++it )
01790   {
01791     QString list;
01792     for ( QPtrListIterator<KDirLister> listit( *it.current() ); listit.current(); ++listit )
01793       list += " 0x" + QString::number( (long)listit.current(), 16 );
01794     kdDebug(7004) << "   " << it.currentKey() << "  " << it.current()->count() << " listers: " << list << endl;
01795   }
01796 
01797   kdDebug(7004) << "urlsCurrentlyListed: " << endl;
01798   QDictIterator< QPtrList<KDirLister> > it2( urlsCurrentlyListed );
01799   for ( ; it2.current() ; ++it2 )
01800   {
01801     QString list;
01802     for ( QPtrListIterator<KDirLister> listit( *it2.current() ); listit.current(); ++listit )
01803       list += " 0x" + QString::number( (long)listit.current(), 16 );
01804     kdDebug(7004) << "   " << it2.currentKey() << "  " << it2.current()->count() << " listers: " << list << endl;
01805   }
01806 
01807   QMap< KIO::ListJob *, QValueList<KIO::UDSEntry> >::Iterator jit = jobs.begin();
01808   kdDebug(7004) << "Jobs: " << endl;
01809   for ( ; jit != jobs.end() ; ++jit )
01810     kdDebug(7004) << "   " << jit.key() << " listing " << joburl( jit.key() ).prettyURL() << ": " << (*jit).count() << " entries." << endl;
01811 
01812   kdDebug(7004) << "Items in cache: " << endl;
01813   QCacheIterator<DirItem> itc( itemsCached );
01814   for ( ; itc.current() ; ++itc )
01815     kdDebug(7004) << "   " << itc.currentKey() << "  rootItem: "
01816                   << ( itc.current()->rootItem ? itc.current()->rootItem->url().prettyURL() : QString("NULL") )
01817                   << ( itc.current()->lstItems ? QString(" with %1 items.").arg(itc.current()->lstItems->count()) : QString(" lstItems=NULL") ) << endl;
01818 }
01819 #endif
01820 
01821 /*********************** -- The new KDirLister -- ************************/
01822 
01823 
01824 KDirLister::KDirLister( bool _delayedMimeTypes )
01825 {
01826   kdDebug(7003) << "+KDirLister" << endl;
01827 
01828   d = new KDirListerPrivate;
01829 
01830   d->complete = true;
01831   d->delayedMimeTypes = _delayedMimeTypes;
01832 
01833   setAutoUpdate( true );
01834   setDirOnlyMode( false );
01835   setShowingDotFiles( false );
01836 
01837   setAutoErrorHandlingEnabled( true, 0 );
01838 }
01839 
01840 KDirLister::~KDirLister()
01841 {
01842   kdDebug(7003) << "-KDirLister" << endl;
01843 
01844   if ( KDirListerCache::exists() )
01845   {
01846     // Stop all running jobs
01847     stop();
01848     s_pCache->forgetDirs( this );
01849   }
01850 
01851   delete d;
01852 }
01853 
01854 bool KDirLister::openURL( const KURL& _url, bool _keep, bool _reload )
01855 {
01856   kdDebug(7003) << k_funcinfo << _url.prettyURL()
01857                 << " keep=" << _keep << " reload=" << _reload << endl;
01858 
01859   // emit the current changes made to avoid an inconsistent treeview
01860   if ( d->changes != NONE && _keep )
01861     emitChanges();
01862 
01863   d->changes = NONE;
01864 
01865   return s_pCache->listDir( this, _url, _keep, _reload );
01866 }
01867 
01868 void KDirLister::stop()
01869 {
01870   kdDebug(7003) << k_funcinfo << endl;
01871   s_pCache->stop( this );
01872 }
01873 
01874 void KDirLister::stop( const KURL& _url )
01875 {
01876   kdDebug(7003) << k_funcinfo << _url.prettyURL() << endl;
01877   s_pCache->stop( this, _url );
01878 }
01879 
01880 bool KDirLister::autoUpdate() const
01881 {
01882   return d->autoUpdate;
01883 }
01884 
01885 void KDirLister::setAutoUpdate( bool _enable )
01886 {
01887   if ( d->autoUpdate == _enable )
01888     return;
01889 
01890   d->autoUpdate = _enable;
01891   s_pCache->setAutoUpdate( this, _enable );
01892 }
01893 
01894 bool KDirLister::showingDotFiles() const
01895 {
01896   return d->isShowingDotFiles;
01897 }
01898 
01899 void KDirLister::setShowingDotFiles( bool _showDotFiles )
01900 {
01901   if ( d->isShowingDotFiles == _showDotFiles )
01902     return;
01903 
01904   d->isShowingDotFiles = _showDotFiles;
01905   d->changes ^= DOT_FILES;
01906 }
01907 
01908 bool KDirLister::dirOnlyMode() const
01909 {
01910   return d->dirOnlyMode;
01911 }
01912 
01913 void KDirLister::setDirOnlyMode( bool _dirsOnly )
01914 {
01915   if ( d->dirOnlyMode == _dirsOnly )
01916     return;
01917 
01918   d->dirOnlyMode = _dirsOnly;
01919   d->changes ^= DIR_ONLY_MODE;
01920 }
01921 
01922 bool KDirLister::autoErrorHandlingEnabled() const
01923 {
01924   return d->autoErrorHandling;
01925 }
01926 
01927 void KDirLister::setAutoErrorHandlingEnabled( bool enable, QWidget* parent )
01928 {
01929   d->autoErrorHandling = enable;
01930   d->errorParent = parent;
01931 }
01932 
01933 const KURL& KDirLister::url() const
01934 {
01935   return d->url;
01936 }
01937 
01938 const KURL::List& KDirLister::directories() const
01939 {
01940   return d->lstDirs;
01941 }
01942 
01943 void KDirLister::emitChanges()
01944 {
01945   if ( d->changes == NONE )
01946     return;
01947 
01948   static const QString& dot = KGlobal::staticQString(".");
01949   static const QString& dotdot = KGlobal::staticQString("..");
01950 
01951   for ( KURL::List::Iterator it = d->lstDirs.begin();
01952         it != d->lstDirs.end(); ++it )
01953   {
01954     KFileItemListIterator kit( *s_pCache->itemsForDir( *it ) );
01955     for ( ; kit.current(); ++kit )
01956     {
01957       if ( (*kit)->text() == dot || (*kit)->text() == dotdot )
01958         continue;
01959 
01960       bool oldMime = true, newMime = true;
01961 
01962       if ( d->changes & MIME_FILTER )
01963       {
01964         oldMime = doMimeFilter( (*kit)->mimetype(), d->oldMimeFilter )
01965                 && doMimeExcludeFilter( (*kit)->mimetype(), d->oldMimeExcludeFilter );
01966         newMime = doMimeFilter( (*kit)->mimetype(), d->mimeFilter )
01967                 && doMimeExcludeFilter( (*kit)->mimetype(), d->mimeExcludeFilter );
01968 
01969         if ( oldMime && !newMime )
01970         {
01971           emit deleteItem( *kit );
01972           continue;
01973         }
01974       }
01975 
01976       if ( d->changes & DIR_ONLY_MODE )
01977       {
01978         // the lister switched to dirOnlyMode
01979         if ( d->dirOnlyMode )
01980         {
01981           if ( !(*kit)->isDir() )
01982             emit deleteItem( *kit );
01983         }
01984         else if ( !(*kit)->isDir() )
01985           addNewItem( *kit );
01986 
01987         continue;
01988       }
01989 
01990       if ( (*kit)->isHidden() )
01991       {
01992         if ( d->changes & DOT_FILES )
01993         {
01994           // the lister switched to dot files mode
01995           if ( d->isShowingDotFiles )
01996             addNewItem( *kit );
01997           else
01998             emit deleteItem( *kit );
01999 
02000           continue;
02001         }
02002       }
02003       else if ( d->changes & NAME_FILTER )
02004       {
02005         bool oldName = (*kit)->isDir() ||
02006                        d->oldFilters.isEmpty() ||
02007                        doNameFilter( (*kit)->text(), d->oldFilters );
02008 
02009         bool newName = (*kit)->isDir() ||
02010                        d->lstFilters.isEmpty() ||
02011                        doNameFilter( (*kit)->text(), d->lstFilters );
02012 
02013         if ( oldName && !newName )
02014         {
02015           emit deleteItem( *kit );
02016           continue;
02017         }
02018         else if ( !oldName && newName )
02019           addNewItem( *kit );
02020       }
02021 
02022       if ( (d->changes & MIME_FILTER) && !oldMime && newMime )
02023         addNewItem( *kit );
02024     }
02025 
02026     emitItems();
02027   }
02028 
02029   d->changes = NONE;
02030 }
02031 
02032 void KDirLister::updateDirectory( const KURL& _u )
02033 {
02034   s_pCache->updateDirectory( _u );
02035 }
02036 
02037 bool KDirLister::isFinished() const
02038 {
02039   return d->complete;
02040 }
02041 
02042 KFileItem *KDirLister::rootItem() const
02043 {
02044   return d->rootFileItem;
02045 }
02046 
02047 KFileItem *KDirLister::findByURL( const KURL& _url ) const
02048 {
02049   return s_pCache->findByURL( this, _url );
02050 }
02051 
02052 KFileItem *KDirLister::findByName( const QString& _name ) const
02053 {
02054   return s_pCache->findByName( this, _name );
02055 }
02056 
02057 #ifndef KDE_NO_COMPAT
02058 KFileItem *KDirLister::find( const KURL& _url ) const
02059 {
02060   return findByURL( _url );
02061 }
02062 #endif
02063 
02064 
02065 // ================ public filter methods ================ //
02066 
02067 void KDirLister::setNameFilter( const QString& nameFilter )
02068 {
02069   if ( !(d->changes & NAME_FILTER) )
02070   {
02071     d->oldFilters = d->lstFilters;
02072     d->lstFilters.setAutoDelete( false );
02073   }
02074 
02075   d->lstFilters.clear();
02076   d->lstFilters.setAutoDelete( true );
02077 
02078   d->nameFilter = nameFilter;
02079 
02080   // Split on white space
02081   QStringList list = QStringList::split( ' ', nameFilter );
02082   for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it )
02083     d->lstFilters.append( new QRegExp(*it, false, true ) );
02084 
02085   d->changes |= NAME_FILTER;
02086 }
02087 
02088 const QString& KDirLister::nameFilter() const
02089 {
02090   return d->nameFilter;
02091 }
02092 
02093 void KDirLister::setMimeFilter( const QStringList& mimeFilter )
02094 {
02095   if ( !(d->changes & MIME_FILTER) )
02096     d->oldMimeFilter = d->mimeFilter;
02097 
02098   if ( mimeFilter.find("all/allfiles") != mimeFilter.end() || 
02099        mimeFilter.find("all/all") != mimeFilter.end() )
02100     d->mimeFilter.clear();
02101   else
02102     d->mimeFilter = mimeFilter;
02103 
02104   d->changes |= MIME_FILTER;
02105 }
02106 
02107 void KDirLister::setMimeExcludeFilter( const QStringList& mimeExcludeFilter )
02108 {
02109   if ( !(d->changes & MIME_FILTER) )
02110     d->oldMimeExcludeFilter = d->mimeExcludeFilter;
02111 
02112   d->mimeExcludeFilter = mimeExcludeFilter;
02113   d->changes |= MIME_FILTER;
02114 }
02115 
02116 
02117 void KDirLister::clearMimeFilter()
02118 {
02119   if ( !(d->changes & MIME_FILTER) )
02120   {
02121     d->oldMimeFilter = d->mimeFilter;
02122     d->oldMimeExcludeFilter = d->mimeExcludeFilter;
02123   }
02124   d->mimeFilter.clear();
02125   d->mimeExcludeFilter.clear();
02126   d->changes |= MIME_FILTER;
02127 }
02128 
02129 const QStringList& KDirLister::mimeFilters() const
02130 {
02131   return d->mimeFilter;
02132 }
02133 
02134 bool KDirLister::matchesFilter( const QString& name ) const
02135 {
02136   return doNameFilter( name, d->lstFilters );
02137 }
02138 
02139 bool KDirLister::matchesMimeFilter( const QString& mime ) const
02140 {
02141   return doMimeFilter( mime, d->mimeFilter ) && doMimeExcludeFilter(mime,d->mimeExcludeFilter);
02142 }
02143 
02144 // ================ protected methods ================ //
02145 
02146 bool KDirLister::matchesFilter( const KFileItem *item ) const
02147 {
02148   Q_ASSERT( item );
02149   static const QString& dotdot = KGlobal::staticQString("..");
02150 
02151   if ( item->text() == dotdot )
02152     return false;
02153 
02154   if ( !d->isShowingDotFiles && item->isHidden() )
02155     return false;
02156 
02157   if ( item->isDir() || d->lstFilters.isEmpty() )
02158     return true;
02159 
02160   return matchesFilter( item->text() );
02161 }
02162 
02163 bool KDirLister::matchesMimeFilter( const KFileItem *item ) const
02164 {
02165   Q_ASSERT( item );
02166   // Don't lose time determining the mimetype if there is no filter
02167   if ( d->mimeFilter.isEmpty() && d->mimeExcludeFilter.isEmpty() )
02168       return true;
02169   return matchesMimeFilter( item->mimetype() );
02170 }
02171 
02172 bool KDirLister::doNameFilter( const QString& name, const QPtrList<QRegExp>& filters ) const
02173 {
02174   for ( QPtrListIterator<QRegExp> it( filters ); it.current(); ++it )
02175     if ( it.current()->exactMatch( name ) )
02176       return true;
02177 
02178   return false;
02179 }
02180 
02181 bool KDirLister::doMimeFilter( const QString& mime, const QStringList& filters ) const
02182 {
02183   if ( filters.isEmpty() )
02184     return true;
02185 
02186   KMimeType::Ptr mimeptr = KMimeType::mimeType(mime);
02187   //kdDebug(7004) << "doMimeFilter: investigating: "<<mimeptr->name()<<endl;
02188   QStringList::ConstIterator it = filters.begin();
02189   for ( ; it != filters.end(); ++it )
02190     if ( mimeptr->is(*it) )
02191       return true;
02192     //else   kdDebug(7004) << "doMimeFilter: compared without result to  "<<*it<<endl;
02193 
02194 
02195   return false;
02196 }
02197 
02198 bool KDirLister::doMimeExcludeFilter( const QString& mime, const QStringList& filters ) const
02199 {
02200   if ( filters.isEmpty() )
02201     return true;
02202 
02203   QStringList::ConstIterator it = filters.begin();
02204   for ( ; it != filters.end(); ++it )
02205     if ( (*it) == mime )
02206       return false;
02207 
02208   return true;
02209 }
02210 
02211 
02212 bool KDirLister::validURL( const KURL& _url ) const
02213 {
02214   return s_pCache->validURL( this, _url );
02215 }
02216 
02217 void KDirLister::handleError( KIO::Job *job )
02218 {
02219   if ( d->autoErrorHandling )
02220     job->showErrorDialog( d->errorParent );
02221 }
02222 
02223 
02224 // ================= private methods ================= //
02225 
02226 void KDirLister::addNewItem( const KFileItem *item )
02227 {
02228   if ( ( d->dirOnlyMode && !item->isDir() ) || !matchesFilter( item ) )
02229     return; // No reason to continue... bailing out here prevents a mimetype scan.
02230 
02231   if ( matchesMimeFilter( item ) )
02232   {
02233     if ( !d->lstNewItems )
02234       d->lstNewItems = new KFileItemList;
02235 
02236     d->lstNewItems->append( item );            // items not filtered
02237   }
02238   else
02239   {
02240     if ( !d->lstMimeFilteredItems )
02241       d->lstMimeFilteredItems = new KFileItemList;
02242 
02243     d->lstMimeFilteredItems->append( item );   // only filtered by mime
02244   }
02245 }
02246 
02247 void KDirLister::addNewItems( const KFileItemList& items )
02248 {
02249   // TODO: make this faster - test if we have a filter at all first
02250   // DF: was this profiled? The matchesFoo() functions should be fast, w/o filters...
02251   // Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good.
02252   // But that's for Qt4, not possible with QPtrList.
02253   for ( KFileItemListIterator kit( items ); kit.current(); ++kit )
02254     addNewItem( *kit );
02255 }
02256 
02257 void KDirLister::aboutToRefreshItem( const KFileItem *item )
02258 {
02259   // The code here follows the logic in addNewItem
02260   if ( ( d->dirOnlyMode && !item->isDir() ) || !matchesFilter( item ) )
02261     d->refreshItemWasFiltered = true;
02262   else if ( !matchesMimeFilter( item ) )
02263     d->refreshItemWasFiltered = true;
02264   else
02265     d->refreshItemWasFiltered = false;
02266 }
02267 
02268 void KDirLister::addRefreshItem( const KFileItem *item )
02269 {
02270   bool isExcluded = (d->dirOnlyMode && !item->isDir()) || !matchesFilter( item );
02271 
02272   if ( !isExcluded && matchesMimeFilter( item ) )
02273   {
02274     if ( d->refreshItemWasFiltered )
02275     {
02276       if ( !d->lstNewItems )
02277         d->lstNewItems = new KFileItemList;
02278 
02279       d->lstNewItems->append( item );
02280     }
02281     else
02282     {
02283       if ( !d->lstRefreshItems )
02284         d->lstRefreshItems = new KFileItemList;
02285 
02286       d->lstRefreshItems->append( item );
02287     }
02288   }
02289   else if ( !d->refreshItemWasFiltered )
02290   {
02291     if ( !d->lstRemoveItems )
02292       d->lstRemoveItems = new KFileItemList;
02293 
02294     // notify the user that the mimetype of a file changed that doesn't match
02295     // a filter or does match an exclude filter
02296     d->lstRemoveItems->append( item );
02297   }
02298 }
02299 
02300 void KDirLister::emitItems()
02301 {
02302   KFileItemList *tmpNew = d->lstNewItems;
02303   d->lstNewItems = 0;
02304 
02305   KFileItemList *tmpMime = d->lstMimeFilteredItems;
02306   d->lstMimeFilteredItems = 0;
02307 
02308   KFileItemList *tmpRefresh = d->lstRefreshItems;
02309   d->lstRefreshItems = 0;
02310 
02311   KFileItemList *tmpRemove = d->lstRemoveItems;
02312   d->lstRemoveItems = 0;
02313 
02314   if ( tmpNew )
02315   {
02316     emit newItems( *tmpNew );
02317     delete tmpNew;
02318   }
02319 
02320   if ( tmpMime )
02321   {
02322     emit itemsFilteredByMime( *tmpMime );
02323     delete tmpMime;
02324   }
02325 
02326   if ( tmpRefresh )
02327   {
02328     emit refreshItems( *tmpRefresh );
02329     delete tmpRefresh;
02330   }
02331 
02332   if ( tmpRemove )
02333   {
02334     for ( KFileItem *tmp = tmpRemove->first(); tmp; tmp = tmpRemove->next() )
02335       emit deleteItem( tmp );
02336     delete tmpRemove;
02337   }
02338 }
02339 
02340 void KDirLister::emitDeleteItem( KFileItem *item )
02341 {
02342   if ( ( d->dirOnlyMode && !item->isDir() ) || !matchesFilter( item ) )
02343     return; // No reason to continue... bailing out here prevents a mimetype scan.
02344   if ( matchesMimeFilter( item ) )
02345     emit deleteItem( item );
02346 }
02347 
02348 
02349 // ================ private slots ================ //
02350 
02351 void KDirLister::slotInfoMessage( KIO::Job *, const QString& message )
02352 {
02353   emit infoMessage( message );
02354 }
02355 
02356 void KDirLister::slotPercent( KIO::Job *job, unsigned long pcnt )
02357 {
02358   d->jobData[static_cast<KIO::ListJob *>(job)].percent = pcnt;
02359 
02360   int result = 0;
02361 
02362   KIO::filesize_t size = 0;
02363 
02364   QMap< KIO::ListJob *, KDirListerPrivate::JobData >::Iterator dataIt = d->jobData.begin();
02365   while ( dataIt != d->jobData.end() )
02366   {
02367     result += (*dataIt).percent * (*dataIt).totalSize;
02368     size += (*dataIt).totalSize;
02369     ++dataIt;
02370   }
02371 
02372   if ( size != 0 )
02373     result /= size;
02374   else
02375     result = 100;
02376   emit percent( result );
02377 }
02378 
02379 void KDirLister::slotTotalSize( KIO::Job *job, KIO::filesize_t size )
02380 {
02381   d->jobData[static_cast<KIO::ListJob *>(job)].totalSize = size;
02382 
02383   KIO::filesize_t result = 0;
02384   QMap< KIO::ListJob *, KDirListerPrivate::JobData >::Iterator dataIt = d->jobData.begin();
02385   while ( dataIt != d->jobData.end() )
02386   {
02387     result += (*dataIt).totalSize;
02388     ++dataIt;
02389   }
02390 
02391   emit totalSize( result );
02392 }
02393 
02394 void KDirLister::slotProcessedSize( KIO::Job *job, KIO::filesize_t size )
02395 {
02396   d->jobData[static_cast<KIO::ListJob *>(job)].processedSize = size;
02397 
02398   KIO::filesize_t result = 0;
02399   QMap< KIO::ListJob *, KDirListerPrivate::JobData >::Iterator dataIt = d->jobData.begin();
02400   while ( dataIt != d->jobData.end() )
02401   {
02402     result += (*dataIt).processedSize;
02403     ++dataIt;
02404   }
02405 
02406   emit processedSize( result );
02407 }
02408 
02409 void KDirLister::slotSpeed( KIO::Job *job, unsigned long spd )
02410 {
02411   d->jobData[static_cast<KIO::ListJob *>(job)].speed = spd;
02412 
02413   int result = 0;
02414   QMap< KIO::ListJob *, KDirListerPrivate::JobData >::Iterator dataIt = d->jobData.begin();
02415   while ( dataIt != d->jobData.end() )
02416   {
02417     result += (*dataIt).speed;
02418     ++dataIt;
02419   }
02420 
02421   emit speed( result );
02422 }
02423 
02424 uint KDirLister::numJobs()
02425 {
02426   return d->jobData.count();
02427 }
02428 
02429 void KDirLister::jobDone( KIO::ListJob *job )
02430 {
02431   d->jobData.remove( job );
02432 }
02433 
02434 void KDirLister::jobStarted( KIO::ListJob *job )
02435 {
02436   KDirListerPrivate::JobData jobData;
02437   jobData.speed = 0;
02438   jobData.percent = 0;
02439   jobData.processedSize = 0;
02440   jobData.totalSize = 0;
02441 
02442   d->jobData.insert( job, jobData );
02443   d->complete = false;
02444 }
02445 
02446 void KDirLister::connectJob( KIO::ListJob *job )
02447 {
02448   connect( job, SIGNAL(infoMessage( KIO::Job *, const QString& )),
02449            this, SLOT(slotInfoMessage( KIO::Job *, const QString& )) );
02450   connect( job, SIGNAL(percent( KIO::Job *, unsigned long )),
02451            this, SLOT(slotPercent( KIO::Job *, unsigned long )) );
02452   connect( job, SIGNAL(totalSize( KIO::Job *, KIO::filesize_t )),
02453            this, SLOT(slotTotalSize( KIO::Job *, KIO::filesize_t )) );
02454   connect( job, SIGNAL(processedSize( KIO::Job *, KIO::filesize_t )),
02455            this, SLOT(slotProcessedSize( KIO::Job *, KIO::filesize_t )) );
02456   connect( job, SIGNAL(speed( KIO::Job *, unsigned long )),
02457            this, SLOT(slotSpeed( KIO::Job *, unsigned long )) );
02458 }
02459 
02460 void KDirLister::setMainWindow( QWidget *window )
02461 {
02462   d->window = window;
02463 }
02464 
02465 QWidget *KDirLister::mainWindow()
02466 {
02467   return d->window;
02468 }
02469 
02470 KFileItemList KDirLister::items( WhichItems which ) const
02471 {
02472     return itemsForDir( url(), which );
02473 }
02474 
02475 KFileItemList KDirLister::itemsForDir( const KURL& dir, WhichItems which ) const
02476 {
02477     KFileItemList result;
02478     KFileItemList *allItems = s_pCache->itemsForDir( dir );
02479     if ( !allItems )
02480         return result;
02481 
02482     if ( which == AllItems )
02483         result = *allItems; // shallow copy
02484     else // only items passing the filters
02485     {
02486         for ( KFileItemListIterator kit( *allItems ); kit.current(); ++kit )
02487         {
02488             KFileItem *item = *kit;
02489             bool isExcluded = (d->dirOnlyMode && !item->isDir()) || !matchesFilter( item );
02490             if ( !isExcluded && matchesMimeFilter( item ) )
02491                 result.append( item );
02492         }
02493     }
02494 
02495     return result;
02496 }
02497 
02498 // to keep BC changes
02499 
02500 void KDirLister::virtual_hook( int, void * )
02501 { /*BASE::virtual_hook( id, data );*/ }
02502 
02503 #include "kdirlister.moc"
02504 #include "kdirlister_p.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys