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