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