• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KIO

kdirlister.cpp

Go to the documentation of this file.
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                  2003-2005 David Faure <faure@kde.org>
00005                  2001-2006 Michael Brade <brade@kde.org>
00006 
00007    This library is free software; you can redistribute it and/or
00008    modify it under the terms of the GNU Library General Public
00009    License as published by the Free Software Foundation; either
00010    version 2 of the License, or (at your option) any later version.
00011 
00012    This library is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015    Library General Public License for more details.
00016 
00017    You should have received a copy of the GNU Library General Public License
00018    along with this library; see the file COPYING.LIB.  If not, write to
00019    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00020    Boston, MA 02110-1301, USA.
00021 */
00022 
00023 #include "kdirlister.h"
00024 #include "kdirlister_p.h"
00025 
00026 #include <QtCore/QRegExp>
00027 
00028 #include <kdebug.h>
00029 #include <kde_file.h>
00030 #include <klocale.h>
00031 #include <kio/job.h>
00032 #include <kio/jobuidelegate.h>
00033 #include <kmessagebox.h>
00034 #include <kglobal.h>
00035 #include <kglobalsettings.h>
00036 #include "kprotocolmanager.h"
00037 #include "kmountpoint.h"
00038 #include <sys/stat.h>
00039 
00040 #include <assert.h>
00041 #include <QFile>
00042 
00043 // Enable this to get printDebug() called often, to see the contents of the cache
00044 //#define DEBUG_CACHE
00045 
00046 // Make really sure it doesn't get activated in the final build
00047 #ifdef NDEBUG
00048 #undef DEBUG_CACHE
00049 #endif
00050 
00051 K_GLOBAL_STATIC(KDirListerCache, kDirListerCache)
00052 
00053 KDirListerCache::KDirListerCache()
00054     : itemsCached( 10 ) // keep the last 10 directories around
00055 {
00056     //kDebug(7004);
00057 
00058   connect( &pendingUpdateTimer, SIGNAL(timeout()), this, SLOT(processPendingUpdates()) );
00059   pendingUpdateTimer.setSingleShot( true );
00060 
00061   connect( KDirWatch::self(), SIGNAL( dirty( const QString& ) ),
00062            this, SLOT( slotFileDirty( const QString& ) ) );
00063   connect( KDirWatch::self(), SIGNAL( created( const QString& ) ),
00064            this, SLOT( slotFileCreated( const QString& ) ) );
00065   connect( KDirWatch::self(), SIGNAL( deleted( const QString& ) ),
00066            this, SLOT( slotFileDeleted( const QString& ) ) );
00067 
00068   kdirnotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this);
00069   connect(kdirnotify, SIGNAL(FileRenamed(QString,QString)), SLOT(slotFileRenamed(QString,QString)));
00070   connect(kdirnotify, SIGNAL(FilesAdded(QString)), SLOT(slotFilesAdded(QString)));
00071   connect(kdirnotify, SIGNAL(FilesChanged(QStringList)), SLOT(slotFilesChanged(QStringList)));
00072   connect(kdirnotify, SIGNAL(FilesRemoved(QStringList)), SLOT(slotFilesRemoved(QStringList)));
00073 
00074   // The use of KUrl::url() in ~DirItem (sendSignal) crashes if the static for QRegExpEngine got deleted already,
00075   // so we need to destroy the KDirListerCache before that.
00076   qAddPostRoutine(kDirListerCache.destroy);
00077 }
00078 
00079 KDirListerCache::~KDirListerCache()
00080 {
00081     //kDebug(7004);
00082 
00083     qDeleteAll(itemsInUse);
00084     itemsInUse.clear();
00085 
00086     itemsCached.clear();
00087     directoryData.clear();
00088 
00089     if ( KDirWatch::exists() )
00090         KDirWatch::self()->disconnect( this );
00091 }
00092 
00093 // setting _reload to true will emit the old files and
00094 // call updateDirectory
00095 bool KDirListerCache::listDir( KDirLister *lister, const KUrl& _u,
00096                                bool _keep, bool _reload )
00097 {
00098   KUrl _url(_u);
00099   _url.cleanPath(); // kill consecutive slashes
00100 
00101   if (!_url.host().isEmpty() && KProtocolInfo::protocolClass(_url.protocol()) == ":local"
00102       && _url.protocol() != "file") {
00103       // ":local" protocols ignore the hostname, so strip it out preventively - #160057
00104       // kio_file is special cased since it does honor the hostname (by redirecting to e.g. smb)
00105       _url.setHost(QString());
00106       if (_keep == false)
00107           emit lister->redirection(_url);
00108   }
00109 
00110   // like this we don't have to worry about trailing slashes any further
00111   _url.adjustPath(KUrl::RemoveTrailingSlash);
00112 
00113   const QString urlStr = _url.url();
00114 
00115   QString resolved;
00116   if (_url.isLocalFile()) {
00117       // Resolve symlinks (#213799)
00118       const QString local = _url.toLocalFile();
00119       resolved = QFileInfo(local).canonicalFilePath();
00120       if (local != resolved)
00121           canonicalUrls[resolved].append(urlStr);
00122       // TODO: remove entry from canonicalUrls again in forgetDirs
00123       // Note: this is why we use a QStringList value in there rather than a QSet:
00124       // we can just remove one entry and not have to worry about other dirlisters
00125       // (the non-unicity of the stringlist gives us the refcounting, basically).
00126   }
00127 
00128   if (!validUrl(lister, _url)) {
00129         kDebug(7004) << lister << "url=" << _url << "not a valid url";
00130         return false;
00131   }
00132 
00133 #ifdef DEBUG_CACHE
00134     printDebug();
00135 #endif
00136   //kDebug(7004) << lister << "url=" << _url << "keep=" << _keep << "reload=" << _reload;
00137 
00138     if (!_keep) {
00139         // stop any running jobs for lister
00140         stop(lister, true /*silent*/);
00141 
00142         // clear our internal list for lister
00143         forgetDirs(lister);
00144 
00145         lister->d->rootFileItem = KFileItem();
00146     } else if (lister->d->lstDirs.contains(_url)) {
00147         // stop the job listing _url for this lister
00148         stop(lister, _url, true /*silent*/);
00149 
00150         // remove the _url as well, it will be added in a couple of lines again!
00151         // forgetDirs with three args does not do this
00152         // TODO: think about moving this into forgetDirs
00153         lister->d->lstDirs.removeAll(_url);
00154 
00155         // clear _url for lister
00156         forgetDirs(lister, _url, true);
00157 
00158         if (lister->d->url == _url)
00159             lister->d->rootFileItem = KFileItem();
00160     }
00161 
00162     lister->d->complete = false;
00163 
00164     lister->d->lstDirs.append(_url);
00165 
00166     if (lister->d->url.isEmpty() || !_keep) // set toplevel URL only if not set yet
00167         lister->d->url = _url;
00168 
00169     DirItem *itemU = itemsInUse.value(urlStr);
00170 
00171     KDirListerCacheDirectoryData& dirData = directoryData[urlStr]; // find or insert
00172 
00173     if (dirData.listersCurrentlyListing.isEmpty()) {
00174         // if there is an update running for _url already we get into
00175         // the following case - it will just be restarted by updateDirectory().
00176 
00177         dirData.listersCurrentlyListing.append(lister);
00178 
00179         DirItem *itemFromCache;
00180         if (itemU || (!_reload && (itemFromCache = itemsCached.take(urlStr)) ) ) {
00181             if (itemU) {
00182                 kDebug(7004) << "Entry already in use:" << _url;
00183                 // if _reload is set, then we'll emit cached items and then updateDirectory.
00184                 if (lister->d->autoUpdate)
00185                     itemU->incAutoUpdate();
00186             } else {
00187                 kDebug(7004) << "Entry in cache:" << _url;
00188                 // In this code path, the itemsFromCache->decAutoUpdate + itemU->incAutoUpdate is optimized out
00189                 itemsInUse.insert(urlStr, itemFromCache);
00190                 itemU = itemFromCache;
00191             }
00192 
00193             emit lister->started(_url);
00194 
00195             // List items from the cache in a delayed manner, just like things would happen
00196             // if we were not using the cache.
00197             new KDirLister::Private::CachedItemsJob(lister, itemU->lstItems, itemU->rootItem, _url, _reload);
00198 
00199         } else {
00200             // dir not in cache or _reload is true
00201             if (_reload) {
00202                 kDebug(7004) << "Reloading directory:" << _url;
00203                 itemsCached.remove(urlStr);
00204             } else {
00205                 kDebug(7004) << "Listing directory:" << _url;
00206             }
00207 
00208             itemU = new DirItem(_url, resolved);
00209             itemsInUse.insert(urlStr, itemU);
00210             if (lister->d->autoUpdate)
00211                 itemU->incAutoUpdate();
00212 
00213 //        // we have a limit of MAX_JOBS_PER_LISTER concurrently running jobs
00214 //        if ( lister->d->numJobs() >= MAX_JOBS_PER_LISTER )
00215 //        {
00216 //          pendingUpdates.insert( _url );
00217 //        }
00218 //        else
00219             {
00220                 KIO::ListJob* job = KIO::listDir(_url, KIO::HideProgressInfo);
00221                 runningListJobs.insert(job, KIO::UDSEntryList());
00222 
00223                 lister->d->jobStarted(job);
00224                 lister->d->connectJob(job);
00225 
00226                 if (lister->d->window)
00227                     job->ui()->setWindow(lister->d->window);
00228 
00229                 connect(job, SIGNAL(entries(KIO::Job *, KIO::UDSEntryList)),
00230                         this, SLOT(slotEntries(KIO::Job *, KIO::UDSEntryList)));
00231                 connect(job, SIGNAL(result(KJob *)),
00232                         this, SLOT(slotResult(KJob *)));
00233                 connect(job, SIGNAL(redirection(KIO::Job *,KUrl)),
00234                         this, SLOT(slotRedirection(KIO::Job *,KUrl)));
00235 
00236                 emit lister->started(_url);
00237             }
00238             //kDebug(7004) << "Entry now being listed by" << dirData.listersCurrentlyListing;
00239         }
00240     } else {
00241 
00242         kDebug(7004) << "Entry currently being listed:" << _url << "by" << dirData.listersCurrentlyListing;
00243 #ifdef DEBUG_CACHE
00244         printDebug();
00245 #endif
00246 
00247         emit lister->started( _url );
00248 
00249         // Maybe listersCurrentlyListing/listersCurrentlyHolding should be QSets?
00250         Q_ASSERT(!dirData.listersCurrentlyListing.contains(lister));
00251         dirData.listersCurrentlyListing.append( lister );
00252 
00253         KIO::ListJob *job = jobForUrl( urlStr );
00254         // job will be 0 if we were listing from cache rather than listing from a kio job.
00255         if( job ) {
00256             lister->d->jobStarted( job );
00257             lister->d->connectJob( job );
00258         }
00259         Q_ASSERT( itemU );
00260 
00261         // List existing items in a delayed manner, just like things would happen
00262         // if we were not using the cache.
00263         //kDebug() << "Listing" << itemU->lstItems.count() << "cached items soon";
00264         new KDirLister::Private::CachedItemsJob(lister, itemU->lstItems, itemU->rootItem, _url, _reload);
00265 
00266 #ifdef DEBUG_CACHE
00267         printDebug();
00268 #endif
00269     }
00270 
00271     return true;
00272 }
00273 
00274 void KDirLister::Private::CachedItemsJob::done()
00275 {
00276     //kDebug() << "lister" << m_lister << "says" << m_lister->d->m_cachedItemsJob << "this=" << this;
00277     Q_ASSERT(m_lister->d->m_cachedItemsJob == this);
00278     kDirListerCache->emitItemsFromCache(m_lister, m_items, m_rootItem, m_url, m_reload, m_emitCompleted);
00279     emitResult();
00280 }
00281 
00282 void KDirListerCache::emitItemsFromCache(KDirLister* lister, const KFileItemList& items, const KFileItem& rootItem, const KUrl& _url, bool _reload, bool _emitCompleted)
00283 {
00284     lister->d->m_cachedItemsJob = 0;
00285 
00286     const QString urlStr = _url.url();
00287     DirItem *itemU = kDirListerCache->itemsInUse.value(urlStr);
00288     Q_ASSERT(itemU); // hey we're listing that dir, so this can't be 0, right?
00289 
00290     KDirLister::Private* kdl = lister->d;
00291 
00292     kdl->complete = false;
00293 
00294     if ( kdl->rootFileItem.isNull() && kdl->url == _url )
00295         kdl->rootFileItem = rootItem;
00296 
00297     //kDebug(7004) << "emitting" << items.count() << "for lister" << lister;
00298     kdl->addNewItems(_url, items);
00299     kdl->emitItems();
00300 
00301     KDirListerCacheDirectoryData& dirData = directoryData[urlStr];
00302     Q_ASSERT(dirData.listersCurrentlyListing.contains(lister));
00303 
00304     // Emit completed, unless we were told not to,
00305     // or if listDir() was called while another directory listing for this dir was happening,
00306     // so we "joined" it. We detect that using jobForUrl to ensure it's a real ListJob,
00307     // not just a lister-specific CachedItemsJob (which wouldn't emit completed for us).
00308     if (_emitCompleted && jobForUrl( urlStr ) == 0) {
00309 
00310         Q_ASSERT(!dirData.listersCurrentlyHolding.contains(lister));
00311         //kDebug(7004) << "Moving from listing to holding, because emitCompleted is true and no job" << lister << urlStr;
00312         dirData.listersCurrentlyHolding.append( lister );
00313         dirData.listersCurrentlyListing.removeAll( lister );
00314 
00315         kdl->complete = true;
00316         emit lister->completed( _url );
00317         emit lister->completed();
00318 
00319         if ( _reload || !itemU->complete ) {
00320             updateDirectory( _url );
00321         }
00322     }
00323 }
00324 
00325 bool KDirListerCache::validUrl( const KDirLister *lister, const KUrl& url ) const
00326 {
00327   if ( !url.isValid() )
00328   {
00329     if ( lister->d->autoErrorHandling )
00330     {
00331       QString tmp = i18n("Malformed URL\n%1", url.prettyUrl() );
00332       KMessageBox::error( lister->d->errorParent, tmp );
00333     }
00334     return false;
00335   }
00336 
00337   if ( !KProtocolManager::supportsListing( url ) )
00338   {
00339     if ( lister->d->autoErrorHandling )
00340     {
00341       QString tmp = i18n("URL cannot be listed\n%1", url.prettyUrl() );
00342       KMessageBox::error( lister->d->errorParent, tmp );
00343     }
00344     return false;
00345   }
00346 
00347   return true;
00348 }
00349 
00350 void KDirListerCache::stop( KDirLister *lister, bool silent )
00351 {
00352 #ifdef DEBUG_CACHE
00353     //printDebug();
00354 #endif
00355     //kDebug(7004) << "lister: " << lister;
00356     bool stopped = false;
00357 
00358     QHash<QString,KDirListerCacheDirectoryData>::iterator dirit = directoryData.begin();
00359     const QHash<QString,KDirListerCacheDirectoryData>::iterator dirend = directoryData.end();
00360     for( ; dirit != dirend ; ++dirit ) {
00361         KDirListerCacheDirectoryData& dirData = dirit.value();
00362         if ( dirData.listersCurrentlyListing.removeAll(lister) ) { // contains + removeAll in one go
00363             // lister is listing url
00364             const QString url = dirit.key();
00365 
00366             //kDebug(7004) << " found lister" << lister << "in list - for" << url;
00367             stopLister(lister, url, dirData, silent);
00368             stopped = true;
00369         }
00370     }
00371 
00372     if (lister->d->m_cachedItemsJob) {
00373         delete lister->d->m_cachedItemsJob;
00374         lister->d->m_cachedItemsJob = 0;
00375         stopped = true;
00376     }
00377 
00378     if ( stopped ) {
00379         if (!silent) {
00380             emit lister->canceled();
00381         }
00382         lister->d->complete = true;
00383     }
00384 
00385     // this is wrong if there is still an update running!
00386     //Q_ASSERT( lister->d->complete );
00387 }
00388 
00389 void KDirListerCache::stop(KDirLister *lister, const KUrl& _u, bool silent)
00390 {
00391     KUrl url(_u);
00392     url.adjustPath( KUrl::RemoveTrailingSlash );
00393     const QString urlStr = url.url();
00394 
00395     if (lister->d->m_cachedItemsJob && lister->d->m_cachedItemsJob->url() == url) {
00396         delete lister->d->m_cachedItemsJob;
00397         lister->d->m_cachedItemsJob = 0;
00398     }
00399 
00400     // TODO: consider to stop all the "child jobs" of url as well
00401     kDebug(7004) << lister << " url=" << url;
00402 
00403     QHash<QString,KDirListerCacheDirectoryData>::iterator dirit = directoryData.find(urlStr);
00404     if (dirit == directoryData.end())
00405         return;
00406     KDirListerCacheDirectoryData& dirData = dirit.value();
00407     if ( dirData.listersCurrentlyListing.removeAll(lister) ) { // contains + removeAll in one go
00408 
00409         //kDebug(7004) << " found lister" << lister << "in list - for" << urlStr;
00410         stopLister(lister, urlStr, dirData, silent);
00411 
00412         if ( lister->d->numJobs() == 0 ) {
00413             lister->d->complete = true;
00414             // we killed the last job for lister
00415             if (!silent) {
00416                 emit lister->canceled();
00417             }
00418             //kDebug(7004) << "Entry now being listed by" << dirData.listersCurrentlyListing;
00419         }
00420     }
00421 }
00422 
00423 // Helper for both stop() methods
00424 void KDirListerCache::stopLister(KDirLister* lister, const QString& url, KDirListerCacheDirectoryData& dirData, bool silent)
00425 {
00426     // Let's just leave the job running.
00427     // After all, update jobs do run for "listersCurrentlyHolding",
00428     // so there's no reason to kill them just because @p lister is now a holder.
00429 
00430     // Move lister to listersCurrentlyHolding
00431     dirData.listersCurrentlyHolding.append(lister);
00432 
00433     if (!silent)
00434         emit lister->canceled(KUrl(url));
00435 }
00436 
00437 void KDirListerCache::setAutoUpdate( KDirLister *lister, bool enable )
00438 {
00439     // IMPORTANT: this method does not check for the current autoUpdate state!
00440 
00441     for ( KUrl::List::const_iterator it = lister->d->lstDirs.constBegin();
00442           it != lister->d->lstDirs.constEnd(); ++it ) {
00443         DirItem* dirItem = itemsInUse.value((*it).url());
00444         Q_ASSERT(dirItem);
00445         if ( enable )
00446             dirItem->incAutoUpdate();
00447         else
00448             dirItem->decAutoUpdate();
00449     }
00450 }
00451 
00452 void KDirListerCache::forgetDirs( KDirLister *lister )
00453 {
00454     //kDebug(7004) << lister;
00455 
00456     emit lister->clear();
00457     // clear lister->d->lstDirs before calling forgetDirs(), so that
00458     // it doesn't contain things that itemsInUse doesn't. When emitting
00459     // the canceled signals, lstDirs must not contain anything that
00460     // itemsInUse does not contain. (otherwise it might crash in findByName()).
00461     const KUrl::List lstDirsCopy = lister->d->lstDirs;
00462     lister->d->lstDirs.clear();
00463 
00464     for ( KUrl::List::const_iterator it = lstDirsCopy.begin();
00465           it != lstDirsCopy.end(); ++it ) {
00466         forgetDirs( lister, *it, false );
00467     }
00468 }
00469 
00470 static bool manually_mounted(const QString& path, const KMountPoint::List& possibleMountPoints)
00471 {
00472     KMountPoint::Ptr mp = possibleMountPoints.findByPath(path);
00473     if (!mp) // not listed in fstab -> yes, manually mounted
00474         return true;
00475     const bool supermount = mp->mountType() == "supermount";
00476     if (supermount) {
00477         return true;
00478     }
00479     // noauto -> manually mounted. Otherwise, mounted at boot time, won't be unmounted any time soon hopefully.
00480     return mp->mountOptions().contains("noauto");
00481 }
00482 
00483 
00484 void KDirListerCache::forgetDirs( KDirLister *lister, const KUrl& _url, bool notify )
00485 {
00486     //kDebug(7004) << lister << " _url: " << _url;
00487 
00488     KUrl url( _url );
00489     url.adjustPath( KUrl::RemoveTrailingSlash );
00490     const QString urlStr = url.url();
00491 
00492     DirectoryDataHash::iterator dit = directoryData.find(urlStr);
00493     if (dit == directoryData.end())
00494         return;
00495     KDirListerCacheDirectoryData& dirData = *dit;
00496     dirData.listersCurrentlyHolding.removeAll(lister);
00497 
00498     // This lister doesn't care for updates running in <url> anymore
00499     KIO::ListJob *job = jobForUrl(urlStr);
00500     if (job)
00501         lister->d->jobDone(job);
00502 
00503     DirItem *item = itemsInUse.value(urlStr);
00504     Q_ASSERT(item);
00505 
00506     if ( dirData.listersCurrentlyHolding.isEmpty() && dirData.listersCurrentlyListing.isEmpty() ) {
00507         // item not in use anymore -> move into cache if complete
00508         directoryData.erase(dit);
00509         itemsInUse.remove( urlStr );
00510 
00511         // this job is a running update which nobody cares about anymore
00512         if ( job ) {
00513             killJob( job );
00514             kDebug(7004) << "Killing update job for " << urlStr;
00515 
00516             // Well, the user of KDirLister doesn't really care that we're stopping
00517             // a background-running job from a previous URL (in listDir) -> commented out.
00518             // stop() already emitted canceled.
00519             //emit lister->canceled( url );
00520             if ( lister->d->numJobs() == 0 ) {
00521                 lister->d->complete = true;
00522                 //emit lister->canceled();
00523             }
00524         }
00525 
00526         if ( notify ) {
00527             lister->d->lstDirs.removeAll( url );
00528             emit lister->clear( url );
00529         }
00530 
00531         if ( item->complete ) {
00532             kDebug(7004) << lister << " item moved into cache: " << url;
00533             itemsCached.insert( urlStr, item );
00534 
00535             const KMountPoint::List possibleMountPoints = KMountPoint::possibleMountPoints(KMountPoint::NeedMountOptions);
00536 
00537             // Should we forget the dir for good, or keep a watch on it?
00538             // Generally keep a watch, except when it would prevent
00539             // unmounting a removable device (#37780)
00540             const bool isLocal = item->url.isLocalFile();
00541             bool isManuallyMounted = false;
00542             bool containsManuallyMounted = false;
00543             if (isLocal) {
00544                 isManuallyMounted = manually_mounted( item->url.toLocalFile(), possibleMountPoints );
00545                 if ( !isManuallyMounted ) {
00546                     // Look for a manually-mounted directory inside
00547                     // If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM
00548                     // I hope this isn't too slow
00549                     KFileItemList::const_iterator kit = item->lstItems.constBegin();
00550                     KFileItemList::const_iterator kend = item->lstItems.constEnd();
00551                     for ( ; kit != kend && !containsManuallyMounted; ++kit )
00552                         if ( (*kit).isDir() && manually_mounted((*kit).url().toLocalFile(), possibleMountPoints) )
00553                             containsManuallyMounted = true;
00554                 }
00555             }
00556 
00557             if ( isManuallyMounted || containsManuallyMounted )
00558             {
00559                 kDebug(7004) << "Not adding a watch on " << item->url << " because it " <<
00560                     ( isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir" );
00561                 item->complete = false; // set to "dirty"
00562             }
00563             else
00564                 item->incAutoUpdate(); // keep watch
00565         }
00566         else
00567         {
00568             delete item;
00569             item = 0;
00570         }
00571     }
00572 
00573     if ( item && lister->d->autoUpdate )
00574         item->decAutoUpdate();
00575 }
00576 
00577 void KDirListerCache::updateDirectory( const KUrl& _dir )
00578 {
00579     kDebug(7004) << _dir;
00580 
00581     QString urlStr = _dir.url(KUrl::RemoveTrailingSlash);
00582     if ( !checkUpdate( urlStr ) )
00583         return;
00584 
00585     // A job can be running to
00586     //   - only list a new directory: the listers are in listersCurrentlyListing
00587     //   - only update a directory: the listers are in listersCurrentlyHolding
00588     //   - update a currently running listing: the listers are in both
00589 
00590     KDirListerCacheDirectoryData& dirData = directoryData[urlStr];
00591     QList<KDirLister *> listers = dirData.listersCurrentlyListing;
00592     QList<KDirLister *> holders = dirData.listersCurrentlyHolding;
00593 
00594     //kDebug(7004) << urlStr << "listers=" << listers << "holders=" << holders;
00595 
00596     // restart the job for _dir if it is running already
00597     bool killed = false;
00598     QWidget *window = 0;
00599     KIO::ListJob *job = jobForUrl( urlStr );
00600     if (job) {
00601         window = job->ui()->window();
00602 
00603         killJob( job );
00604         killed = true;
00605 
00606         foreach ( KDirLister *kdl, listers )
00607             kdl->d->jobDone( job );
00608 
00609         foreach ( KDirLister *kdl, holders )
00610             kdl->d->jobDone( job );
00611     } else {
00612         // Emit any cached items.
00613         // updateDirectory() is about the diff compared to the cached items...
00614         Q_FOREACH(KDirLister *kdl, listers) {
00615             if (kdl->d->m_cachedItemsJob) {
00616                 KDirLister::Private::CachedItemsJob* job = kdl->d->m_cachedItemsJob;
00617                 job->setEmitCompleted(false);
00618                 job->done(); // sets kdl->d->m_cachedItemsJob to 0
00619                 delete job;
00620                 killed = true;
00621             }
00622         }
00623     }
00624     //kDebug(7004) << "Killed=" << killed;
00625 
00626     // we don't need to emit canceled signals since we only replaced the job,
00627     // the listing is continuing.
00628 
00629     if (!(listers.isEmpty() || killed)) {
00630         kWarning() << "The unexpected happened.";
00631         kWarning() << "listers for" << _dir << "=" << listers;
00632         kWarning() << "job=" << job;
00633         Q_FOREACH(KDirLister *kdl, listers) {
00634             kDebug() << "lister" << kdl << "m_cachedItemsJob=" << kdl->d->m_cachedItemsJob;
00635         }
00636 #ifndef NDEBUG
00637         printDebug();
00638 #endif
00639     }
00640     Q_ASSERT( listers.isEmpty() || killed );
00641 
00642     job = KIO::listDir( _dir, KIO::HideProgressInfo );
00643     runningListJobs.insert( job, KIO::UDSEntryList() );
00644 
00645     connect( job, SIGNAL(entries( KIO::Job *, const KIO::UDSEntryList & )),
00646              this, SLOT(slotUpdateEntries( KIO::Job *, const KIO::UDSEntryList & )) );
00647     connect( job, SIGNAL(result( KJob * )),
00648              this, SLOT(slotUpdateResult( KJob * )) );
00649 
00650     kDebug(7004) << "update started in" << _dir;
00651 
00652     foreach ( KDirLister *kdl, listers ) {
00653         kdl->d->jobStarted( job );
00654     }
00655 
00656     if ( !holders.isEmpty() ) {
00657         if ( !killed ) {
00658             bool first = true;
00659             foreach ( KDirLister *kdl, holders ) {
00660                 kdl->d->jobStarted( job );
00661                 if ( first && kdl->d->window ) {
00662                     first = false;
00663                     job->ui()->setWindow( kdl->d->window );
00664                 }
00665                 emit kdl->started( _dir );
00666             }
00667         } else {
00668             job->ui()->setWindow( window );
00669 
00670             foreach ( KDirLister *kdl, holders ) {
00671                 kdl->d->jobStarted( job );
00672             }
00673         }
00674     }
00675 }
00676 
00677 bool KDirListerCache::checkUpdate( const QString& _dir )
00678 {
00679   if ( !itemsInUse.contains(_dir) )
00680   {
00681     DirItem *item = itemsCached[_dir];
00682     if ( item && item->complete )
00683     {
00684       item->complete = false;
00685       item->decAutoUpdate();
00686       // Hmm, this debug output might include login/password from the _dir URL.
00687       //kDebug(7004) << "directory " << _dir << " not in use, marked dirty.";
00688     }
00689     //else
00690       //kDebug(7004) << "aborted, directory " << _dir << " not in cache.";
00691 
00692     return false;
00693   }
00694   else
00695     return true;
00696 }
00697 
00698 KFileItem KDirListerCache::itemForUrl( const KUrl& url ) const
00699 {
00700     KFileItem *item = findByUrl( 0, url );
00701     if (item) {
00702         return *item;
00703     } else {
00704         return KFileItem();
00705     }
00706 }
00707 
00708 KDirListerCache::DirItem *KDirListerCache::dirItemForUrl(const KUrl& dir) const
00709 {
00710     const QString urlStr = dir.url(KUrl::RemoveTrailingSlash);
00711     DirItem *item = itemsInUse.value(urlStr);
00712     if ( !item )
00713         item = itemsCached[urlStr];
00714     return item;
00715 }
00716 
00717 KFileItemList *KDirListerCache::itemsForDir(const KUrl& dir) const
00718 {
00719     DirItem *item = dirItemForUrl(dir);
00720     return item ? &item->lstItems : 0;
00721 }
00722 
00723 KFileItem KDirListerCache::findByName( const KDirLister *lister, const QString& _name ) const
00724 {
00725     Q_ASSERT(lister);
00726 
00727     for (KUrl::List::const_iterator it = lister->d->lstDirs.constBegin();
00728          it != lister->d->lstDirs.constEnd(); ++it) {
00729         DirItem* dirItem = itemsInUse.value((*it).url());
00730         Q_ASSERT(dirItem);
00731         const KFileItem item = dirItem->lstItems.findByName(_name);
00732         if (!item.isNull())
00733             return item;
00734     }
00735 
00736     return KFileItem();
00737 }
00738 
00739 KFileItem *KDirListerCache::findByUrl( const KDirLister *lister, const KUrl& _u ) const
00740 {
00741     KUrl url(_u);
00742     url.adjustPath(KUrl::RemoveTrailingSlash);
00743 
00744     KUrl parentDir(url);
00745     parentDir.setPath( parentDir.directory() );
00746 
00747     DirItem* dirItem = dirItemForUrl(parentDir);
00748     if (dirItem) {
00749         // If lister is set, check that it contains this dir
00750         if (!lister || lister->d->lstDirs.contains(parentDir)) {
00751             KFileItemList::iterator it = dirItem->lstItems.begin();
00752             const KFileItemList::iterator end = dirItem->lstItems.end();
00753             for (; it != end ; ++it) {
00754                 if ((*it).url() == url) {
00755                     return &*it;
00756                 }
00757             }
00758         }
00759     }
00760 
00761     // Maybe _u is a directory itself? (see KDirModelTest::testChmodDirectory)
00762     // We check this last, though, we prefer returning a kfileitem with an actual
00763     // name if possible (and we make it '.' for root items later).
00764     dirItem = dirItemForUrl(url);
00765     if (dirItem && !dirItem->rootItem.isNull() && dirItem->rootItem.url() == url) {
00766         // If lister is set, check that it contains this dir
00767         if (!lister || lister->d->lstDirs.contains(url)) {
00768             return &dirItem->rootItem;
00769         }
00770     }
00771 
00772     return 0;
00773 }
00774 
00775 void KDirListerCache::slotFilesAdded( const QString &dir /*url*/ ) // from KDirNotify signals
00776 {
00777     KUrl urlDir(dir);
00778     kDebug(7004) << urlDir; // output urls, not qstrings, since they might contain a password
00779     if (urlDir.isLocalFile()) {
00780         Q_FOREACH(const QString& u, directoriesForCanonicalPath(urlDir.path())) {
00781             updateDirectory(KUrl(u));
00782         }
00783     } else {
00784         updateDirectory(urlDir);
00785     }
00786 }
00787 
00788 void KDirListerCache::slotFilesRemoved( const QStringList &fileList ) // from KDirNotify signals
00789 {
00790     // TODO: handling of symlinks-to-directories isn't done here,
00791     // because I'm not sure how to do it and keep the performance ok...
00792     slotFilesRemoved(KUrl::List(fileList));
00793 }
00794 
00795 void KDirListerCache::slotFilesRemoved(const KUrl::List& fileList)
00796 {
00797     //kDebug(7004) << fileList.count();
00798     // Group notifications by parent dirs (usually there would be only one parent dir)
00799     QMap<QString, KFileItemList> removedItemsByDir;
00800     KUrl::List deletedSubdirs;
00801 
00802     for (KUrl::List::const_iterator it = fileList.begin(); it != fileList.end() ; ++it) {
00803         const KUrl url(*it);
00804         DirItem* dirItem = dirItemForUrl(url); // is it a listed directory?
00805         if (dirItem) {
00806             deletedSubdirs.append(url);
00807             if (!dirItem->rootItem.isNull()) {
00808                 removedItemsByDir[url.url()].append(dirItem->rootItem);
00809             }
00810         }
00811 
00812         KUrl parentDir(url);
00813         parentDir.setPath(parentDir.directory());
00814         dirItem = dirItemForUrl(parentDir);
00815         if (!dirItem)
00816             continue;
00817         for (KFileItemList::iterator fit = dirItem->lstItems.begin(), fend = dirItem->lstItems.end(); fit != fend ; ++fit) {
00818             if ((*fit).url() == url) {
00819                 const KFileItem fileitem = *fit;
00820                 removedItemsByDir[parentDir.url()].append(fileitem);
00821                 // If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case.
00822                 if (fileitem.isNull() || fileitem.isDir()) {
00823                     deletedSubdirs.append(url);
00824                 }
00825                 dirItem->lstItems.erase(fit); // remove fileitem from list
00826                 break;
00827             }
00828         }
00829     }
00830 
00831     QMap<QString, KFileItemList>::const_iterator rit = removedItemsByDir.constBegin();
00832     for(; rit != removedItemsByDir.constEnd(); ++rit) {
00833         // Tell the views about it before calling deleteDir.
00834         // They might need the subdirs' file items (see the dirtree).
00835         DirectoryDataHash::const_iterator dit = directoryData.constFind(rit.key());
00836         if (dit != directoryData.constEnd()) {
00837             itemsDeleted((*dit).listersCurrentlyHolding, rit.value());
00838         }
00839     }
00840 
00841     Q_FOREACH(const KUrl& url, deletedSubdirs) {
00842         // in case of a dir, check if we have any known children, there's much to do in that case
00843         // (stopping jobs, removing dirs from cache etc.)
00844         deleteDir(url);
00845     }
00846 }
00847 
00848 void KDirListerCache::slotFilesChanged( const QStringList &fileList ) // from KDirNotify signals
00849 {
00850     //kDebug(7004) << fileList;
00851     KUrl::List dirsToUpdate;
00852     QStringList::const_iterator it = fileList.begin();
00853     for (; it != fileList.end() ; ++it) {
00854         KUrl url( *it );
00855         KFileItem *fileitem = findByUrl(0, url);
00856         if (!fileitem) {
00857             kDebug(7004) << "item not found for" << url;
00858             continue;
00859         }
00860         if (url.isLocalFile()) {
00861             pendingUpdates.insert(*it); // delegate the work to processPendingUpdates
00862         } else {
00863             pendingRemoteUpdates.insert(fileitem);
00864             // For remote files, we won't be able to figure out the new information,
00865             // we have to do a update (directory listing)
00866             KUrl dir(url);
00867             dir.setPath(dir.directory());
00868             if (!dirsToUpdate.contains(dir))
00869                 dirsToUpdate.prepend(dir);
00870         }
00871     }
00872 
00873     KUrl::List::const_iterator itdir = dirsToUpdate.constBegin();
00874     for (; itdir != dirsToUpdate.constEnd() ; ++itdir)
00875         updateDirectory( *itdir );
00876     // ## TODO problems with current jobs listing/updating that dir
00877     // ( see kde-2.2.2's kdirlister )
00878 
00879     processPendingUpdates();
00880 }
00881 
00882 void KDirListerCache::slotFileRenamed( const QString &_src, const QString &_dst ) // from KDirNotify signals
00883 {
00884   KUrl src( _src );
00885   KUrl dst( _dst );
00886   kDebug(7004) << src << "->" << dst;
00887 #ifdef DEBUG_CACHE
00888   printDebug();
00889 #endif
00890 
00891     KUrl oldurl(src);
00892     oldurl.adjustPath( KUrl::RemoveTrailingSlash );
00893     KFileItem *fileitem = findByUrl(0, oldurl);
00894     if (!fileitem) {
00895         kDebug(7004) << "Item not found:" << oldurl;
00896         return;
00897     }
00898 
00899     const KFileItem oldItem = *fileitem;
00900 
00901     // Dest already exists? Was overwritten then (testcase: #151851)
00902     // We better emit it as deleted -before- doing the renaming, otherwise
00903     // the "update" mechanism will emit the old one as deleted and
00904     // kdirmodel will delete the new (renamed) one!
00905     KFileItem* existingDestItem = findByUrl(0, dst);
00906     if (existingDestItem) {
00907         //kDebug() << dst << "already existed, let's delete it";
00908         slotFilesRemoved(dst);
00909     }
00910 
00911     // If the item had a UDS_URL as well as UDS_NAME set, the user probably wants
00912     // to be updating the name only (since they can't see the URL).
00913     // Check to see if a URL exists, and if so, if only the file part has changed,
00914     // only update the name and not the underlying URL.
00915     bool nameOnly = !fileitem->entry().stringValue( KIO::UDSEntry::UDS_URL ).isEmpty();
00916     nameOnly &= src.directory( KUrl::IgnoreTrailingSlash | KUrl::AppendTrailingSlash ) ==
00917                 dst.directory( KUrl::IgnoreTrailingSlash | KUrl::AppendTrailingSlash );
00918 
00919     if (!nameOnly && fileitem->isDir()) {
00920         renameDir( src, dst );
00921         // #172945 - if the fileitem was the root item of a DirItem that was just removed from the cache,
00922         // then it's a dangling pointer now...
00923         fileitem = findByUrl(0, oldurl);
00924         if (!fileitem) //deleted from cache altogether, #188807
00925             return;
00926     }
00927 
00928     // Now update the KFileItem representing that file or dir (not exclusive with the above!)
00929     if (!oldItem.isLocalFile() && !oldItem.localPath().isEmpty()) { // it uses UDS_LOCAL_PATH? ouch, needs an update then
00930         slotFilesChanged( QStringList() << src.url() );
00931     } else {
00932         if( nameOnly )
00933             fileitem->setName( dst.fileName() );
00934         else
00935             fileitem->setUrl( dst );
00936         fileitem->refreshMimeType();
00937         fileitem->determineMimeType();
00938         QSet<KDirLister*> listers = emitRefreshItem( oldItem, *fileitem );
00939         Q_FOREACH(KDirLister * kdl, listers) {
00940             kdl->d->emitItems();
00941         }
00942     }
00943 
00944 #ifdef DEBUG_CACHE
00945     printDebug();
00946 #endif
00947 }
00948 
00949 QSet<KDirLister*> KDirListerCache::emitRefreshItem(const KFileItem& oldItem, const KFileItem& fileitem)
00950 {
00951     //kDebug(7004) << "old:" << oldItem.name() << oldItem.url()
00952     //             << "new:" << fileitem.name() << fileitem.url();
00953     // Look whether this item was shown in any view, i.e. held by any dirlister
00954     KUrl parentDir( oldItem.url() );
00955     parentDir.setPath( parentDir.directory() );
00956     const QString parentDirURL = parentDir.url();
00957     DirectoryDataHash::iterator dit = directoryData.find(parentDirURL);
00958     QList<KDirLister *> listers;
00959     // Also look in listersCurrentlyListing, in case the user manages to rename during a listing
00960     if (dit != directoryData.end())
00961         listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing;
00962     if (oldItem.isDir()) {
00963         // For a directory, look for dirlisters where it's the root item.
00964         dit = directoryData.find(oldItem.url().url());
00965         if (dit != directoryData.end())
00966             listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing;
00967     }
00968     QSet<KDirLister*> listersToRefresh;
00969     Q_FOREACH(KDirLister *kdl, listers) {
00970         // For a directory, look for dirlisters where it's the root item.
00971         KUrl directoryUrl(oldItem.url());
00972         if (oldItem.isDir() && kdl->d->rootFileItem == oldItem) {
00973             const KFileItem oldRootItem = kdl->d->rootFileItem;
00974             kdl->d->rootFileItem = fileitem;
00975             kdl->d->addRefreshItem(directoryUrl, oldRootItem, fileitem);
00976         } else {
00977             directoryUrl.setPath(directoryUrl.directory());
00978             kdl->d->addRefreshItem(directoryUrl, oldItem, fileitem);
00979         }
00980         listersToRefresh.insert(kdl);
00981     }
00982     return listersToRefresh;
00983 }
00984 
00985 QStringList KDirListerCache::directoriesForCanonicalPath(const QString& dir) const
00986 {
00987     QStringList dirs;
00988     dirs << dir;
00989     dirs << canonicalUrls.value(dir).toSet().toList(); /* make unique; there are faster ways, but this is really small anyway */
00990 
00991     if (dirs.count() > 1)
00992         kDebug() << dir << "known as" << dirs;
00993 
00994     return dirs;
00995 }
00996 
00997 // private slots
00998 
00999 // Called by KDirWatch - usually when a dir we're watching has been modified,
01000 // but it can also be called for a file.
01001 void KDirListerCache::slotFileDirty( const QString& path )
01002 {
01003     kDebug(7004) << path;
01004     // File or dir?
01005     KDE_struct_stat buff;
01006     if ( KDE::stat( path, &buff ) != 0 )
01007         return; // error
01008     const bool isDir = S_ISDIR(buff.st_mode);
01009     KUrl url(path);
01010     url.adjustPath(KUrl::RemoveTrailingSlash);
01011     if (isDir) {
01012         Q_FOREACH(const QString& dir, directoriesForCanonicalPath(url.path())) {
01013             handleDirDirty(dir);
01014         }
01015     } else {
01016         Q_FOREACH(const QString& dir, directoriesForCanonicalPath(url.directory())) {
01017             KUrl aliasUrl(dir);
01018             aliasUrl.addPath(url.fileName());
01019             handleFileDirty(aliasUrl);
01020         }
01021     }
01022 }
01023 
01024 // Called by slotFileDirty
01025 void KDirListerCache::handleDirDirty(const KUrl& url)
01026 {
01027     // A dir: launch an update job if anyone cares about it
01028 
01029     // This also means we can forget about pending updates to individual files in that dir
01030     const QString dirPath = url.toLocalFile(KUrl::AddTrailingSlash);
01031     QMutableSetIterator<QString> pendingIt(pendingUpdates);
01032     while (pendingIt.hasNext()) {
01033         const QString updPath = pendingIt.next();
01034         //kDebug(7004) << "had pending update" << updPath;
01035         if (updPath.startsWith(dirPath) &&
01036             updPath.indexOf('/', dirPath.length()) == -1) { // direct child item
01037             kDebug(7004) << "forgetting about individual update to" << updPath;
01038             pendingIt.remove();
01039         }
01040     }
01041 
01042     updateDirectory(url);
01043 }
01044 
01045 // Called by slotFileDirty
01046 void KDirListerCache::handleFileDirty(const KUrl& url)
01047 {
01048     // A file: do we know about it already?
01049     KFileItem* existingItem = findByUrl(0, url);
01050     if (!existingItem) {
01051         // No - update the parent dir then
01052         KUrl dir(url);
01053         dir.setPath(url.directory());
01054         updateDirectory(dir);
01055     } else {
01056         // A known file: delay updating it, FAM is flooding us with events
01057         const QString filePath = url.toLocalFile();
01058         if (!pendingUpdates.contains(filePath)) {
01059             KUrl dir(url);
01060             dir.setPath(dir.directory());
01061             if (checkUpdate(dir.url())) {
01062                 pendingUpdates.insert(filePath);
01063                 if (!pendingUpdateTimer.isActive())
01064                     pendingUpdateTimer.start(500);
01065             }
01066         }
01067     }
01068 }
01069 
01070 void KDirListerCache::slotFileCreated( const QString& path ) // from KDirWatch
01071 {
01072     kDebug(7004) << path;
01073     // XXX: how to avoid a complete rescan here?
01074     // We'd need to stat that one file separately and refresh the item(s) for it.
01075     KUrl fileUrl(path);
01076     slotFilesAdded(fileUrl.directory());
01077 }
01078 
01079 void KDirListerCache::slotFileDeleted( const QString& path ) // from KDirWatch
01080 {
01081     kDebug(7004) << path;
01082     KUrl u( path );
01083     QStringList fileUrls;
01084     Q_FOREACH(KUrl url, directoriesForCanonicalPath(u.directory())) {
01085         url.addPath(u.fileName());
01086         fileUrls << url.url();
01087     }
01088     slotFilesRemoved(fileUrls);
01089 }
01090 
01091 void KDirListerCache::slotEntries( KIO::Job *job, const KIO::UDSEntryList &entries )
01092 {
01093     KUrl url(joburl( static_cast<KIO::ListJob *>(job) ));
01094     url.adjustPath(KUrl::RemoveTrailingSlash);
01095     QString urlStr = url.url();
01096 
01097     //kDebug(7004) << "new entries for " << url;
01098 
01099     DirItem *dir = itemsInUse.value(urlStr);
01100     if (!dir) {
01101         kError(7004) << "Internal error: job is listing" << url << "but itemsInUse only knows about" << itemsInUse.keys();
01102         Q_ASSERT( dir );
01103         return;
01104     }
01105 
01106     DirectoryDataHash::iterator dit = directoryData.find(urlStr);
01107     if (dit == directoryData.end()) {
01108         kError(7004) << "Internal error: job is listing" << url << "but directoryData doesn't know about that url, only about:" << directoryData.keys();
01109         Q_ASSERT(dit != directoryData.end());
01110         return;
01111     }
01112     KDirListerCacheDirectoryData& dirData = *dit;
01113     if (dirData.listersCurrentlyListing.isEmpty()) {
01114         kError(7004) << "Internal error: job is listing" << url << "but directoryData says no listers are currently listing " << urlStr;
01115         Q_ASSERT( !dirData.listersCurrentlyListing.isEmpty() );
01116         return;
01117     }
01118 
01119     // check if anyone wants the mimetypes immediately
01120     bool delayedMimeTypes = true;
01121     foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
01122         delayedMimeTypes &= kdl->d->delayedMimeTypes;
01123 
01124     KIO::UDSEntryList::const_iterator it = entries.begin();
01125     const KIO::UDSEntryList::const_iterator end = entries.end();
01126     for ( ; it != end; ++it )
01127     {
01128         const QString name = (*it).stringValue( KIO::UDSEntry::UDS_NAME );
01129 
01130         Q_ASSERT( !name.isEmpty() );
01131         if ( name.isEmpty() )
01132             continue;
01133 
01134         if ( name == "." )
01135         {
01136             Q_ASSERT( dir->rootItem.isNull() );
01137             // Try to reuse an existing KFileItem (if we listed the parent dir)
01138             // rather than creating a new one. There are many reasons:
01139             // 1) renames and permission changes to the item would have to emit the signals
01140             // twice, otherwise, so that both views manage to recognize the item.
01141             // 2) with kio_ftp we can only know that something is a symlink when
01142             // listing the parent, so prefer that item, which has more info.
01143             // Note that it gives a funky name() to the root item, rather than "." ;)
01144             dir->rootItem = itemForUrl(url);
01145             if (dir->rootItem.isNull())
01146                 dir->rootItem = KFileItem( *it, url, delayedMimeTypes, true  );
01147 
01148             foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
01149                 if ( kdl->d->rootFileItem.isNull() && kdl->d->url == url )
01150                     kdl->d->rootFileItem = dir->rootItem;
01151         }
01152         else if ( name != ".." )
01153         {
01154             KFileItem item( *it, url, delayedMimeTypes, true );
01155 
01156             //kDebug(7004)<< "Adding item: " << item.url();
01157             dir->lstItems.append( item );
01158 
01159             foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
01160                 kdl->d->addNewItem(url, item);
01161         }
01162     }
01163 
01164     foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
01165         kdl->d->emitItems();
01166 }
01167 
01168 void KDirListerCache::slotResult( KJob *j )
01169 {
01170 #ifdef DEBUG_CACHE
01171   printDebug();
01172 #endif
01173 
01174   Q_ASSERT( j );
01175   KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
01176   runningListJobs.remove( job );
01177 
01178   KUrl jobUrl(joburl( job ));
01179   jobUrl.adjustPath(KUrl::RemoveTrailingSlash);  // need remove trailing slashes again, in case of redirections
01180   QString jobUrlStr = jobUrl.url();
01181 
01182   kDebug(7004) << "finished listing" << jobUrl;
01183 
01184   DirectoryDataHash::iterator dit = directoryData.find(jobUrlStr);
01185   if (dit == directoryData.end()) {
01186     kError() << "Nothing found in directoryData for URL" << jobUrlStr;
01187 #ifndef NDEBUG
01188     printDebug();
01189 #endif
01190     Q_ASSERT(dit != directoryData.end());
01191     return;
01192   }
01193   KDirListerCacheDirectoryData& dirData = *dit;
01194   if ( dirData.listersCurrentlyListing.isEmpty() ) {
01195     kError() << "OOOOPS, nothing in directoryData.listersCurrentlyListing for" << jobUrlStr;
01196     // We're about to assert; dump the current state...
01197 #ifndef NDEBUG
01198     printDebug();
01199 #endif
01200     Q_ASSERT( !dirData.listersCurrentlyListing.isEmpty() );
01201   }
01202   QList<KDirLister *> listers = dirData.listersCurrentlyListing;
01203 
01204   // move all listers to the holding list, do it before emitting
01205   // the signals to make sure it exists in KDirListerCache in case someone
01206   // calls listDir during the signal emission
01207   Q_ASSERT( dirData.listersCurrentlyHolding.isEmpty() );
01208   dirData.moveListersWithoutCachedItemsJob(jobUrl);
01209 
01210   if ( job->error() )
01211   {
01212     foreach ( KDirLister *kdl, listers )
01213     {
01214       kdl->d->jobDone( job );
01215       kdl->handleError( job );
01216       emit kdl->canceled( jobUrl );
01217       if ( kdl->d->numJobs() == 0 )
01218       {
01219         kdl->d->complete = true;
01220         emit kdl->canceled();
01221       }
01222     }
01223   }
01224   else
01225   {
01226     DirItem *dir = itemsInUse.value(jobUrlStr);
01227     Q_ASSERT( dir );
01228     dir->complete = true;
01229 
01230     foreach ( KDirLister* kdl, listers )
01231     {
01232       kdl->d->jobDone( job );
01233       emit kdl->completed( jobUrl );
01234       if ( kdl->d->numJobs() == 0 )
01235       {
01236         kdl->d->complete = true;
01237         emit kdl->completed();
01238       }
01239     }
01240   }
01241 
01242   // TODO: hmm, if there was an error and job is a parent of one or more
01243   // of the pending urls we should cancel it/them as well
01244   processPendingUpdates();
01245 
01246 #ifdef DEBUG_CACHE
01247   printDebug();
01248 #endif
01249 }
01250 
01251 void KDirListerCache::slotRedirection( KIO::Job *j, const KUrl& url )
01252 {
01253     Q_ASSERT( j );
01254     KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
01255 
01256     KUrl oldUrl(job->url());  // here we really need the old url!
01257     KUrl newUrl(url);
01258 
01259     // strip trailing slashes
01260     oldUrl.adjustPath(KUrl::RemoveTrailingSlash);
01261     newUrl.adjustPath(KUrl::RemoveTrailingSlash);
01262 
01263     if ( oldUrl == newUrl ) {
01264         kDebug(7004) << "New redirection url same as old, giving up.";
01265         return;
01266     } else if (newUrl.isEmpty()) {
01267         kDebug(7004) << "New redirection url is empty, giving up.";
01268         return;
01269     }
01270 
01271     const QString oldUrlStr = oldUrl.url();
01272     const QString newUrlStr = newUrl.url();
01273 
01274     kDebug(7004) << oldUrl << "->" << newUrl;
01275 
01276 #ifdef DEBUG_CACHE
01277     // Can't do that here. KDirListerCache::joburl() will use the new url already,
01278     // while our data structures haven't been updated yet -> assert fail.
01279     //printDebug();
01280 #endif
01281 
01282     // I don't think there can be dirItems that are children of oldUrl.
01283     // Am I wrong here? And even if so, we don't need to delete them, right?
01284     // DF: redirection happens before listDir emits any item. Makes little sense otherwise.
01285 
01286     // oldUrl cannot be in itemsCached because only completed items are moved there
01287     DirItem *dir = itemsInUse.take(oldUrlStr);
01288     Q_ASSERT( dir );
01289 
01290     DirectoryDataHash::iterator dit = directoryData.find(oldUrlStr);
01291     Q_ASSERT(dit != directoryData.end());
01292     KDirListerCacheDirectoryData oldDirData = *dit;
01293     directoryData.erase(dit);
01294     Q_ASSERT( !oldDirData.listersCurrentlyListing.isEmpty() );
01295     const QList<KDirLister *> listers = oldDirData.listersCurrentlyListing;
01296     Q_ASSERT( !listers.isEmpty() );
01297 
01298     foreach ( KDirLister *kdl, listers ) {
01299         kdl->d->redirect(oldUrlStr, newUrl, false /*clear items*/);
01300     }
01301 
01302     // when a lister was stopped before the job emits the redirection signal, the old url will
01303     // also be in listersCurrentlyHolding
01304     const QList<KDirLister *> holders = oldDirData.listersCurrentlyHolding;
01305     foreach ( KDirLister *kdl, holders ) {
01306         kdl->d->jobStarted( job );
01307         // do it like when starting a new list-job that will redirect later
01308         // TODO: maybe don't emit started if there's an update running for newUrl already?
01309         emit kdl->started( oldUrl );
01310 
01311         kdl->d->redirect(oldUrl, newUrl, false /*clear items*/);
01312     }
01313 
01314     DirItem *newDir = itemsInUse.value(newUrlStr);
01315     if ( newDir ) {
01316         kDebug(7004) << newUrl << "already in use";
01317 
01318         // only in this case there can newUrl already be in listersCurrentlyListing or listersCurrentlyHolding
01319         delete dir;
01320 
01321         // get the job if one's running for newUrl already (can be a list-job or an update-job), but
01322         // do not return this 'job', which would happen because of the use of redirectionURL()
01323         KIO::ListJob *oldJob = jobForUrl( newUrlStr, job );
01324 
01325         // listers of newUrl with oldJob: forget about the oldJob and use the already running one
01326         // which will be converted to an updateJob
01327         KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
01328 
01329         QList<KDirLister *>& curListers = newDirData.listersCurrentlyListing;
01330         if ( !curListers.isEmpty() ) {
01331             kDebug(7004) << "and it is currently listed";
01332 
01333             Q_ASSERT( oldJob );  // ?!
01334 
01335             foreach ( KDirLister *kdl, curListers ) { // listers of newUrl
01336                 kdl->d->jobDone( oldJob );
01337 
01338                 kdl->d->jobStarted( job );
01339                 kdl->d->connectJob( job );
01340             }
01341 
01342             // append listers of oldUrl with newJob to listers of newUrl with oldJob
01343             foreach ( KDirLister *kdl, listers )
01344                 curListers.append( kdl );
01345         } else {
01346             curListers = listers;
01347         }
01348 
01349         if ( oldJob )         // kill the old job, be it a list-job or an update-job
01350             killJob( oldJob );
01351 
01352         // holders of newUrl: use the already running job which will be converted to an updateJob
01353         QList<KDirLister *>& curHolders = newDirData.listersCurrentlyHolding;
01354         if ( !curHolders.isEmpty() ) {
01355             kDebug(7004) << "and it is currently held.";
01356 
01357             foreach ( KDirLister *kdl, curHolders ) {  // holders of newUrl
01358                 kdl->d->jobStarted( job );
01359                 emit kdl->started( newUrl );
01360             }
01361 
01362             // append holders of oldUrl to holders of newUrl
01363             foreach ( KDirLister *kdl, holders )
01364                 curHolders.append( kdl );
01365         } else {
01366             curHolders = holders;
01367         }
01368 
01369 
01370         // emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed
01371         // TODO: make this a separate method?
01372         foreach ( KDirLister *kdl, listers + holders ) {
01373             if ( kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl )
01374                 kdl->d->rootFileItem = newDir->rootItem;
01375 
01376             kdl->d->addNewItems(newUrl, newDir->lstItems);
01377             kdl->d->emitItems();
01378         }
01379     } else if ( (newDir = itemsCached.take( newUrlStr )) ) {
01380         kDebug(7004) << newUrl << "is unused, but already in the cache.";
01381 
01382         delete dir;
01383         itemsInUse.insert( newUrlStr, newDir );
01384         KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
01385         newDirData.listersCurrentlyListing = listers;
01386         newDirData.listersCurrentlyHolding = holders;
01387 
01388         // emit old items: listers, holders
01389         foreach ( KDirLister *kdl, listers + holders ) {
01390             if ( kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl )
01391                 kdl->d->rootFileItem = newDir->rootItem;
01392 
01393             kdl->d->addNewItems(newUrl, newDir->lstItems);
01394             kdl->d->emitItems();
01395         }
01396     } else {
01397         kDebug(7004) << newUrl << "has not been listed yet.";
01398 
01399         dir->rootItem = KFileItem();
01400         dir->lstItems.clear();
01401         dir->redirect( newUrl );
01402         itemsInUse.insert( newUrlStr, dir );
01403         KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
01404         newDirData.listersCurrentlyListing = listers;
01405         newDirData.listersCurrentlyHolding = holders;
01406 
01407         if ( holders.isEmpty() ) {
01408 #ifdef DEBUG_CACHE
01409             printDebug();
01410 #endif
01411             return; // only in this case the job doesn't need to be converted,
01412         }
01413     }
01414 
01415     // make the job an update job
01416     job->disconnect( this );
01417 
01418     connect( job, SIGNAL(entries( KIO::Job *, const KIO::UDSEntryList & )),
01419              this, SLOT(slotUpdateEntries( KIO::Job *, const KIO::UDSEntryList & )) );
01420     connect( job, SIGNAL(result( KJob * )),
01421              this, SLOT(slotUpdateResult( KJob * )) );
01422 
01423     // FIXME: autoUpdate-Counts!!
01424 
01425 #ifdef DEBUG_CACHE
01426     printDebug();
01427 #endif
01428 }
01429 
01430 struct KDirListerCache::ItemInUseChange
01431 {
01432     ItemInUseChange(const QString& old, const QString& newU, DirItem* di)
01433         : oldUrl(old), newUrl(newU), dirItem(di) {}
01434     QString oldUrl;
01435     QString newUrl;
01436     DirItem* dirItem;
01437 };
01438 
01439 void KDirListerCache::renameDir( const KUrl &oldUrl, const KUrl &newUrl )
01440 {
01441     kDebug(7004) << oldUrl << "->" << newUrl;
01442     const QString oldUrlStr = oldUrl.url(KUrl::RemoveTrailingSlash);
01443     const QString newUrlStr = newUrl.url(KUrl::RemoveTrailingSlash);
01444 
01445     // Not enough. Also need to look at any child dir, even sub-sub-sub-dir.
01446     //DirItem *dir = itemsInUse.take( oldUrlStr );
01447     //emitRedirections( oldUrl, url );
01448 
01449     QLinkedList<ItemInUseChange> itemsToChange;
01450     QSet<KDirLister *> listers;
01451 
01452     // Look at all dirs being listed/shown
01453     QHash<QString, DirItem *>::iterator itu = itemsInUse.begin();
01454     const QHash<QString, DirItem *>::iterator ituend = itemsInUse.end();
01455     for (; itu != ituend ; ++itu) {
01456         DirItem *dir = itu.value();
01457         KUrl oldDirUrl ( itu.key() );
01458         //kDebug(7004) << "itemInUse:" << oldDirUrl;
01459         // Check if this dir is oldUrl, or a subfolder of it
01460         if ( oldUrl.isParentOf( oldDirUrl ) ) {
01461             // TODO should use KUrl::cleanpath like isParentOf does
01462             QString relPath = oldDirUrl.path().mid( oldUrl.path().length() );
01463 
01464             KUrl newDirUrl( newUrl ); // take new base
01465             if ( !relPath.isEmpty() )
01466                 newDirUrl.addPath( relPath ); // add unchanged relative path
01467             //kDebug(7004) << "new url=" << newDirUrl;
01468 
01469             // Update URL in dir item and in itemsInUse
01470             dir->redirect( newDirUrl );
01471 
01472             itemsToChange.append(ItemInUseChange(oldDirUrl.url(KUrl::RemoveTrailingSlash),
01473                                                  newDirUrl.url(KUrl::RemoveTrailingSlash),
01474                                                  dir));
01475             // Rename all items under that dir
01476 
01477             for ( KFileItemList::iterator kit = dir->lstItems.begin(), kend = dir->lstItems.end();
01478                   kit != kend ; ++kit )
01479             {
01480                 const KFileItem oldItem = *kit;
01481 
01482                 const KUrl oldItemUrl ((*kit).url());
01483                 const QString oldItemUrlStr( oldItemUrl.url(KUrl::RemoveTrailingSlash) );
01484                 KUrl newItemUrl( oldItemUrl );
01485                 newItemUrl.setPath( newDirUrl.path() );
01486                 newItemUrl.addPath( oldItemUrl.fileName() );
01487                 kDebug(7004) << "renaming" << oldItemUrl << "to" << newItemUrl;
01488                 (*kit).setUrl(newItemUrl);
01489 
01490                 listers |= emitRefreshItem(oldItem, *kit);
01491             }
01492             emitRedirections( oldDirUrl, newDirUrl );
01493         }
01494     }
01495 
01496     Q_FOREACH(KDirLister * kdl, listers) {
01497         kdl->d->emitItems();
01498     }
01499 
01500     // Do the changes to itemsInUse out of the loop to avoid messing up iterators,
01501     // and so that emitRefreshItem can find the stuff in the hash.
01502     foreach(const ItemInUseChange& i, itemsToChange) {
01503         itemsInUse.remove(i.oldUrl);
01504         itemsInUse.insert(i.newUrl, i.dirItem);
01505     }
01506 
01507     // Is oldUrl a directory in the cache?
01508     // Remove any child of oldUrl from the cache - even if the renamed dir itself isn't in it!
01509     removeDirFromCache( oldUrl );
01510     // TODO rename, instead.
01511 }
01512 
01513 // helper for renameDir, not used for redirections from KIO::listDir().
01514 void KDirListerCache::emitRedirections( const KUrl &oldUrl, const KUrl &newUrl )
01515 {
01516     kDebug(7004) << oldUrl << "->" << newUrl;
01517     const QString oldUrlStr = oldUrl.url(KUrl::RemoveTrailingSlash);
01518     const QString newUrlStr = newUrl.url(KUrl::RemoveTrailingSlash);
01519 
01520     KIO::ListJob *job = jobForUrl( oldUrlStr );
01521     if ( job )
01522         killJob( job );
01523 
01524     // Check if we were listing this dir. Need to abort and restart with new name in that case.
01525     DirectoryDataHash::iterator dit = directoryData.find(oldUrlStr);
01526     if ( dit == directoryData.end() )
01527         return;
01528     const QList<KDirLister *> listers = (*dit).listersCurrentlyListing;
01529     const QList<KDirLister *> holders = (*dit).listersCurrentlyHolding;
01530 
01531     KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
01532 
01533     // Tell the world that the job listing the old url is dead.
01534     foreach ( KDirLister *kdl, listers ) {
01535         if ( job )
01536             kdl->d->jobDone( job );
01537 
01538         emit kdl->canceled( oldUrl );
01539     }
01540     newDirData.listersCurrentlyListing += listers;
01541 
01542     // Check if we are currently displaying this directory (odds opposite wrt above)
01543     foreach ( KDirLister *kdl, holders ) {
01544         if ( job )
01545             kdl->d->jobDone( job );
01546     }
01547     newDirData.listersCurrentlyHolding += holders;
01548     directoryData.erase(dit);
01549 
01550     if ( !listers.isEmpty() ) {
01551         updateDirectory( newUrl );
01552 
01553         // Tell the world about the new url
01554         foreach ( KDirLister *kdl, listers )
01555             emit kdl->started( newUrl );
01556     }
01557 
01558     // And notify the dirlisters of the redirection
01559     foreach ( KDirLister *kdl, holders ) {
01560         kdl->d->redirect(oldUrl, newUrl, true /*keep items*/);
01561     }
01562 }
01563 
01564 void KDirListerCache::removeDirFromCache( const KUrl& dir )
01565 {
01566     kDebug(7004) << dir;
01567     const QList<QString> cachedDirs = itemsCached.keys(); // seems slow, but there's no qcache iterator...
01568     foreach(const QString& cachedDir, cachedDirs) {
01569         if ( dir.isParentOf( KUrl( cachedDir ) ) )
01570             itemsCached.remove( cachedDir );
01571     }
01572 }
01573 
01574 void KDirListerCache::slotUpdateEntries( KIO::Job* job, const KIO::UDSEntryList& list )
01575 {
01576     runningListJobs[static_cast<KIO::ListJob*>(job)] += list;
01577 }
01578 
01579 void KDirListerCache::slotUpdateResult( KJob * j )
01580 {
01581     Q_ASSERT( j );
01582     KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
01583 
01584     KUrl jobUrl (joburl( job ));
01585     jobUrl.adjustPath(KUrl::RemoveTrailingSlash);  // need remove trailing slashes again, in case of redirections
01586     QString jobUrlStr (jobUrl.url());
01587 
01588     kDebug(7004) << "finished update" << jobUrl;
01589 
01590     KDirListerCacheDirectoryData& dirData = directoryData[jobUrlStr];
01591     // Collect the dirlisters which were listing the URL using that ListJob
01592     // plus those that were already holding that URL - they all get updated.
01593     dirData.moveListersWithoutCachedItemsJob(jobUrl);
01594     QList<KDirLister *> listers = dirData.listersCurrentlyHolding;
01595     listers += dirData.listersCurrentlyListing;
01596 
01597     // once we are updating dirs that are only in the cache this will fail!
01598     Q_ASSERT( !listers.isEmpty() );
01599 
01600     if ( job->error() ) {
01601         foreach ( KDirLister* kdl, listers ) {
01602             kdl->d->jobDone( job );
01603 
01604             //don't bother the user
01605             //kdl->handleError( job );
01606 
01607             emit kdl->canceled( jobUrl );
01608             if ( kdl->d->numJobs() == 0 ) {
01609                 kdl->d->complete = true;
01610                 emit kdl->canceled();
01611             }
01612         }
01613 
01614         runningListJobs.remove( job );
01615 
01616         // TODO: if job is a parent of one or more
01617         // of the pending urls we should cancel them
01618         processPendingUpdates();
01619         return;
01620     }
01621 
01622     DirItem *dir = itemsInUse.value(jobUrlStr, 0);
01623     if (!dir) {
01624         kError(7004) << "Internal error: itemsInUse did not contain" << jobUrlStr;
01625 #ifndef NDEBUG
01626         printDebug();
01627 #endif
01628         Q_ASSERT(dir);
01629     } else {
01630         dir->complete = true;
01631     }
01632 
01633     // check if anyone wants the mimetypes immediately
01634     bool delayedMimeTypes = true;
01635     foreach ( KDirLister *kdl, listers )
01636         delayedMimeTypes &= kdl->d->delayedMimeTypes;
01637 
01638     QHash<QString, KFileItem*> fileItems; // fileName -> KFileItem*
01639 
01640     // Unmark all items in url
01641     for ( KFileItemList::iterator kit = dir->lstItems.begin(), kend = dir->lstItems.end() ; kit != kend ; ++kit )
01642     {
01643         (*kit).unmark();
01644         fileItems.insert( (*kit).name(), &*kit );
01645     }
01646 
01647     const KIO::UDSEntryList& buf = runningListJobs.value( job );
01648     KIO::UDSEntryList::const_iterator it = buf.constBegin();
01649     const KIO::UDSEntryList::const_iterator end = buf.constEnd();
01650     for ( ; it != end; ++it )
01651     {
01652         // Form the complete url
01653         KFileItem item( *it, jobUrl, delayedMimeTypes, true );
01654 
01655         const QString name = item.name();
01656         Q_ASSERT( !name.isEmpty() );
01657 
01658         // we duplicate the check for dotdot here, to avoid iterating over
01659         // all items again and checking in matchesFilter() that way.
01660         if ( name.isEmpty() || name == ".." )
01661             continue;
01662 
01663         if ( name == "." )
01664         {
01665             // if the update was started before finishing the original listing
01666             // there is no root item yet
01667             if ( dir->rootItem.isNull() )
01668             {
01669                 dir->rootItem = item;
01670 
01671                 foreach ( KDirLister *kdl, listers )
01672                     if ( kdl->d->rootFileItem.isNull() && kdl->d->url == jobUrl )
01673                         kdl->d->rootFileItem = dir->rootItem;
01674             }
01675             continue;
01676         }
01677 
01678         // Find this item
01679         if (KFileItem* tmp = fileItems.value(item.name()))
01680         {
01681             QSet<KFileItem*>::iterator pru_it = pendingRemoteUpdates.find(tmp);
01682             const bool inPendingRemoteUpdates = (pru_it != pendingRemoteUpdates.end());
01683 
01684             // check if something changed for this file, using KFileItem::cmp()
01685             if (!tmp->cmp( item ) || inPendingRemoteUpdates) {
01686 
01687                 if (inPendingRemoteUpdates) {
01688                     pendingRemoteUpdates.erase(pru_it);
01689                 }
01690 
01691                 //kDebug(7004) << "file changed:" << tmp->name();
01692 
01693                 const KFileItem oldItem = *tmp;
01694                 *tmp = item;
01695                 foreach ( KDirLister *kdl, listers )
01696                     kdl->d->addRefreshItem(jobUrl, oldItem, *tmp);
01697             }
01698             //kDebug(7004) << "marking" << tmp;
01699             tmp->mark();
01700         }
01701         else // this is a new file
01702         {
01703             //kDebug(7004) << "new file:" << name;
01704 
01705             KFileItem pitem(item);
01706             pitem.mark();
01707             dir->lstItems.append( pitem );
01708 
01709             foreach ( KDirLister *kdl, listers )
01710                 kdl->d->addNewItem(jobUrl, pitem);
01711         }
01712     }
01713 
01714     runningListJobs.remove( job );
01715 
01716     deleteUnmarkedItems( listers, dir->lstItems );
01717 
01718     foreach ( KDirLister *kdl, listers ) {
01719         kdl->d->emitItems();
01720 
01721         kdl->d->jobDone( job );
01722 
01723         emit kdl->completed( jobUrl );
01724         if ( kdl->d->numJobs() == 0 )
01725         {
01726             kdl->d->complete = true;
01727             emit kdl->completed();
01728         }
01729     }
01730 
01731     // TODO: hmm, if there was an error and job is a parent of one or more
01732     // of the pending urls we should cancel it/them as well
01733     processPendingUpdates();
01734 }
01735 
01736 // private
01737 
01738 KIO::ListJob *KDirListerCache::jobForUrl( const QString& url, KIO::ListJob *not_job )
01739 {
01740   QMap< KIO::ListJob *, KIO::UDSEntryList >::const_iterator it = runningListJobs.constBegin();
01741   while ( it != runningListJobs.constEnd() )
01742   {
01743     KIO::ListJob *job = it.key();
01744     if ( joburl( job ).url(KUrl::RemoveTrailingSlash) == url && job != not_job )
01745        return job;
01746     ++it;
01747   }
01748   return 0;
01749 }
01750 
01751 const KUrl& KDirListerCache::joburl( KIO::ListJob *job )
01752 {
01753   if ( job->redirectionUrl().isValid() )
01754      return job->redirectionUrl();
01755   else
01756      return job->url();
01757 }
01758 
01759 void KDirListerCache::killJob( KIO::ListJob *job )
01760 {
01761   runningListJobs.remove( job );
01762   job->disconnect( this );
01763   job->kill();
01764 }
01765 
01766 void KDirListerCache::deleteUnmarkedItems( const QList<KDirLister *>& listers, KFileItemList &lstItems )
01767 {
01768     KFileItemList deletedItems;
01769     // Find all unmarked items and delete them
01770     QMutableListIterator<KFileItem> kit(lstItems);
01771     while (kit.hasNext()) {
01772         const KFileItem& item = kit.next();
01773         if (!item.isMarked()) {
01774             //kDebug(7004) << "deleted:" << item.name() << &item;
01775             deletedItems.append(item);
01776             kit.remove();
01777         }
01778     }
01779     if (!deletedItems.isEmpty())
01780         itemsDeleted(listers, deletedItems);
01781 }
01782 
01783 void KDirListerCache::itemsDeleted(const QList<KDirLister *>& listers, const KFileItemList& deletedItems)
01784 {
01785     Q_FOREACH(KDirLister *kdl, listers) {
01786         kdl->d->emitItemsDeleted(deletedItems);
01787     }
01788 
01789     Q_FOREACH(const KFileItem& item, deletedItems) {
01790         if (item.isDir())
01791             deleteDir(item.url());
01792     }
01793 }
01794 
01795 void KDirListerCache::deleteDir( const KUrl& dirUrl )
01796 {
01797     //kDebug() << dirUrl;
01798     // unregister and remove the children of the deleted item.
01799     // Idea: tell all the KDirListers that they should forget the dir
01800     //       and then remove it from the cache.
01801 
01802     // Separate itemsInUse iteration and calls to forgetDirs (which modify itemsInUse)
01803     KUrl::List affectedItems;
01804 
01805     QHash<QString, DirItem *>::iterator itu = itemsInUse.begin();
01806     const QHash<QString, DirItem *>::iterator ituend = itemsInUse.end();
01807     for ( ; itu != ituend; ++itu ) {
01808         const KUrl deletedUrl( itu.key() );
01809         if ( dirUrl.isParentOf( deletedUrl ) ) {
01810             affectedItems.append(deletedUrl);
01811         }
01812     }
01813 
01814     foreach(const KUrl& deletedUrl, affectedItems) {
01815         const QString deletedUrlStr = deletedUrl.url();
01816         // stop all jobs for deletedUrlStr
01817         DirectoryDataHash::iterator dit = directoryData.find(deletedUrlStr);
01818         if (dit != directoryData.end()) {
01819             // we need a copy because stop modifies the list
01820             QList<KDirLister *> listers = (*dit).listersCurrentlyListing;
01821             foreach ( KDirLister *kdl, listers )
01822                 stop( kdl, deletedUrl );
01823             // tell listers holding deletedUrl to forget about it
01824             // this will stop running updates for deletedUrl as well
01825 
01826             // we need a copy because forgetDirs modifies the list
01827             QList<KDirLister *> holders = (*dit).listersCurrentlyHolding;
01828             foreach ( KDirLister *kdl, holders ) {
01829                 // lister's root is the deleted item
01830                 if ( kdl->d->url == deletedUrl )
01831                 {
01832                     // tell the view first. It might need the subdirs' items (which forgetDirs will delete)
01833                     if ( !kdl->d->rootFileItem.isNull() ) {
01834                         emit kdl->deleteItem( kdl->d->rootFileItem );
01835                         emit kdl->itemsDeleted(KFileItemList() << kdl->d->rootFileItem);
01836                     }
01837                     forgetDirs( kdl );
01838                     kdl->d->rootFileItem = KFileItem();
01839                 }
01840                 else
01841                 {
01842                     const bool treeview = kdl->d->lstDirs.count() > 1;
01843                     if ( !treeview )
01844                     {
01845                         emit kdl->clear();
01846                         kdl->d->lstDirs.clear();
01847                     }
01848                     else
01849                         kdl->d->lstDirs.removeAll( deletedUrl );
01850 
01851                     forgetDirs( kdl, deletedUrl, treeview );
01852                 }
01853             }
01854         }
01855 
01856         // delete the entry for deletedUrl - should not be needed, it's in
01857         // items cached now
01858         int count = itemsInUse.remove( deletedUrlStr );
01859         Q_ASSERT( count == 0 );
01860         Q_UNUSED( count ); //keep gcc "unused variable" complaining quiet when in release mode
01861     }
01862 
01863     // remove the children from the cache
01864     removeDirFromCache( dirUrl );
01865 }
01866 
01867 // delayed updating of files, FAM is flooding us with events
01868 void KDirListerCache::processPendingUpdates()
01869 {
01870     QSet<KDirLister *> listers;
01871     foreach(const QString& file, pendingUpdates) { // always a local path
01872         kDebug(7004) << file;
01873         KUrl u(file);
01874         KFileItem *item = findByUrl( 0, u ); // search all items
01875         if ( item ) {
01876             // we need to refresh the item, because e.g. the permissions can have changed.
01877             KFileItem oldItem = *item;
01878             item->refresh();
01879             listers |= emitRefreshItem( oldItem, *item );
01880         }
01881     }
01882     pendingUpdates.clear();
01883     Q_FOREACH(KDirLister * kdl, listers) {
01884         kdl->d->emitItems();
01885     }
01886 }
01887 
01888 #ifndef NDEBUG
01889 void KDirListerCache::printDebug()
01890 {
01891     kDebug(7004) << "Items in use:";
01892     QHash<QString, DirItem *>::const_iterator itu = itemsInUse.constBegin();
01893     const QHash<QString, DirItem *>::const_iterator ituend = itemsInUse.constEnd();
01894     for ( ; itu != ituend ; ++itu ) {
01895         kDebug(7004) << "   " << itu.key() << "URL:" << itu.value()->url
01896                      << "rootItem:" << ( !itu.value()->rootItem.isNull() ? itu.value()->rootItem.url() : KUrl() )
01897                      << "autoUpdates refcount:" << itu.value()->autoUpdates
01898                      << "complete:" << itu.value()->complete
01899                      << QString("with %1 items.").arg(itu.value()->lstItems.count());
01900     }
01901 
01902     QList<KDirLister*> listersWithoutJob;
01903     kDebug(7004) << "Directory data:";
01904     DirectoryDataHash::const_iterator dit = directoryData.constBegin();
01905     for ( ; dit != directoryData.constEnd(); ++dit )
01906     {
01907         QString list;
01908         foreach ( KDirLister* listit, (*dit).listersCurrentlyListing )
01909             list += " 0x" + QString::number( (qlonglong)listit, 16 );
01910         kDebug(7004) << "  " << dit.key() << (*dit).listersCurrentlyListing.count() << "listers:" << list;
01911         foreach ( KDirLister* listit, (*dit).listersCurrentlyListing ) {
01912             if (listit->d->m_cachedItemsJob) {
01913                 kDebug(7004) << "  Lister" << listit << "has CachedItemsJob" << listit->d->m_cachedItemsJob;
01914             } else if (KIO::ListJob* listJob = jobForUrl(dit.key())) {
01915                 kDebug(7004) << "  Lister" << listit << "has ListJob" << listJob;
01916             } else {
01917                 listersWithoutJob.append(listit);
01918             }
01919         }
01920 
01921         list.clear();
01922         foreach ( KDirLister* listit, (*dit).listersCurrentlyHolding )
01923             list += " 0x" + QString::number( (qlonglong)listit, 16 );
01924         kDebug(7004) << "  " << dit.key() << (*dit).listersCurrentlyHolding.count() << "holders:" << list;
01925     }
01926 
01927     QMap< KIO::ListJob *, KIO::UDSEntryList >::Iterator jit = runningListJobs.begin();
01928     kDebug(7004) << "Jobs:";
01929     for ( ; jit != runningListJobs.end() ; ++jit )
01930         kDebug(7004) << "   " << jit.key() << "listing" << joburl( jit.key() ) << ":" << (*jit).count() << "entries.";
01931 
01932     kDebug(7004) << "Items in cache:";
01933     const QList<QString> cachedDirs = itemsCached.keys();
01934     foreach(const QString& cachedDir, cachedDirs) {
01935         DirItem* dirItem = itemsCached.object(cachedDir);
01936         kDebug(7004) << "   " << cachedDir << "rootItem:"
01937                      << (!dirItem->rootItem.isNull() ? dirItem->rootItem.url().prettyUrl() : QString("NULL") )
01938                      << "with" << dirItem->lstItems.count() << "items.";
01939     }
01940 
01941     // Abort on listers without jobs -after- showing the full dump. Easier debugging.
01942     Q_FOREACH(KDirLister* listit, listersWithoutJob) {
01943         kFatal() << "HUH? Lister" << listit << "is supposed to be listing, but has no job!";
01944     }
01945 }
01946 #endif
01947 
01948 
01949 KDirLister::KDirLister( QObject* parent )
01950     : QObject(parent), d(new Private(this))
01951 {
01952     //kDebug(7003) << "+KDirLister";
01953 
01954     d->complete = true;
01955 
01956     setAutoUpdate( true );
01957     setDirOnlyMode( false );
01958     setShowingDotFiles( false );
01959 
01960     setAutoErrorHandlingEnabled( true, 0 );
01961 }
01962 
01963 KDirLister::~KDirLister()
01964 {
01965     //kDebug(7003) << "-KDirLister";
01966 
01967     // Stop all running jobs
01968     if (!kDirListerCache.isDestroyed()) {
01969         stop();
01970         kDirListerCache->forgetDirs( this );
01971     }
01972 
01973     delete d;
01974 }
01975 
01976 bool KDirLister::openUrl( const KUrl& _url, OpenUrlFlags _flags )
01977 {
01978     // emit the current changes made to avoid an inconsistent treeview
01979     if (d->hasPendingChanges && (_flags & Keep))
01980         emitChanges();
01981 
01982     d->hasPendingChanges = false;
01983 
01984     return kDirListerCache->listDir( this, _url, _flags & Keep, _flags & Reload );
01985 }
01986 
01987 void KDirLister::stop()
01988 {
01989     kDirListerCache->stop( this );
01990 }
01991 
01992 void KDirLister::stop( const KUrl& _url )
01993 {
01994     kDirListerCache->stop( this, _url );
01995 }
01996 
01997 bool KDirLister::autoUpdate() const
01998 {
01999     return d->autoUpdate;
02000 }
02001 
02002 void KDirLister::setAutoUpdate( bool _enable )
02003 {
02004     if ( d->autoUpdate == _enable )
02005         return;
02006 
02007     d->autoUpdate = _enable;
02008     kDirListerCache->setAutoUpdate( this, _enable );
02009 }
02010 
02011 bool KDirLister::showingDotFiles() const
02012 {
02013   return d->settings.isShowingDotFiles;
02014 }
02015 
02016 void KDirLister::setShowingDotFiles( bool _showDotFiles )
02017 {
02018   if ( d->settings.isShowingDotFiles == _showDotFiles )
02019     return;
02020 
02021   d->prepareForSettingsChange();
02022   d->settings.isShowingDotFiles = _showDotFiles;
02023 }
02024 
02025 bool KDirLister::dirOnlyMode() const
02026 {
02027   return d->settings.dirOnlyMode;
02028 }
02029 
02030 void KDirLister::setDirOnlyMode( bool _dirsOnly )
02031 {
02032   if ( d->settings.dirOnlyMode == _dirsOnly )
02033     return;
02034 
02035   d->prepareForSettingsChange();
02036   d->settings.dirOnlyMode = _dirsOnly;
02037 }
02038 
02039 bool KDirLister::autoErrorHandlingEnabled() const
02040 {
02041   return d->autoErrorHandling;
02042 }
02043 
02044 void KDirLister::setAutoErrorHandlingEnabled( bool enable, QWidget* parent )
02045 {
02046   d->autoErrorHandling = enable;
02047   d->errorParent = parent;
02048 }
02049 
02050 KUrl KDirLister::url() const
02051 {
02052   return d->url;
02053 }
02054 
02055 KUrl::List KDirLister::directories() const
02056 {
02057   return d->lstDirs;
02058 }
02059 
02060 void KDirLister::emitChanges()
02061 {
02062     d->emitChanges();
02063 }
02064 
02065 void KDirLister::Private::emitChanges()
02066 {
02067     if (!hasPendingChanges)
02068         return;
02069 
02070     // reset 'hasPendingChanges' now, in case of recursion
02071     // (testcase: enabling recursive scan in ktorrent, #174920)
02072     hasPendingChanges = false;
02073 
02074     const Private::FilterSettings newSettings = settings;
02075     settings = oldSettings; // temporarily
02076 
02077     // Mark all items that are currently visible
02078     Q_FOREACH(const KUrl& dir, lstDirs) {
02079         KFileItemList* itemList = kDirListerCache->itemsForDir(dir);
02080         KFileItemList::iterator kit = itemList->begin();
02081         const KFileItemList::iterator kend = itemList->end();
02082         for (; kit != kend; ++kit) {
02083             if (isItemVisible(*kit) && m_parent->matchesMimeFilter(*kit))
02084                 (*kit).mark();
02085             else
02086                 (*kit).unmark();
02087         }
02088     }
02089 
02090     settings = newSettings;
02091 
02092     Q_FOREACH(const KUrl& dir, lstDirs) {
02093         KFileItemList deletedItems;
02094 
02095         KFileItemList* itemList = kDirListerCache->itemsForDir(dir);
02096         KFileItemList::iterator kit = itemList->begin();
02097         const KFileItemList::iterator kend = itemList->end();
02098         for (; kit != kend; ++kit) {
02099             KFileItem& item = *kit;
02100             const QString text = item.text();
02101             if (text == "." || text == "..")
02102                 continue;
02103             const bool nowVisible = isItemVisible(item) && m_parent->matchesMimeFilter(item);
02104             if (nowVisible && !item.isMarked())
02105                 addNewItem(dir, item); // takes care of emitting newItem or itemsFilteredByMime
02106             else if (!nowVisible && item.isMarked())
02107                 deletedItems.append(*kit);
02108         }
02109         if (!deletedItems.isEmpty()) {
02110             emit m_parent->itemsDeleted(deletedItems);
02111             // for compat
02112             Q_FOREACH(const KFileItem& item, deletedItems)
02113                 emit m_parent->deleteItem(item);
02114         }
02115         emitItems();
02116     }
02117     oldSettings = settings;
02118 }
02119 
02120 void KDirLister::updateDirectory( const KUrl& _u )
02121 {
02122   kDirListerCache->updateDirectory( _u );
02123 }
02124 
02125 bool KDirLister::isFinished() const
02126 {
02127   return d->complete;
02128 }
02129 
02130 KFileItem KDirLister::rootItem() const
02131 {
02132   return d->rootFileItem;
02133 }
02134 
02135 KFileItem KDirLister::findByUrl( const KUrl& _url ) const
02136 {
02137   KFileItem *item = kDirListerCache->findByUrl( this, _url );
02138   if (item) {
02139       return *item;
02140   } else {
02141       return KFileItem();
02142   }
02143 }
02144 
02145 KFileItem KDirLister::findByName( const QString& _name ) const
02146 {
02147   return kDirListerCache->findByName( this, _name );
02148 }
02149 
02150 
02151 // ================ public filter methods ================ //
02152 
02153 void KDirLister::setNameFilter( const QString& nameFilter )
02154 {
02155     if (d->nameFilter == nameFilter)
02156         return;
02157 
02158     d->prepareForSettingsChange();
02159 
02160     d->settings.lstFilters.clear();
02161     d->nameFilter = nameFilter;
02162     // Split on white space
02163     const QStringList list = nameFilter.split( ' ', QString::SkipEmptyParts );
02164     for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
02165         d->settings.lstFilters.append(QRegExp(*it, Qt::CaseInsensitive, QRegExp::Wildcard));
02166 }
02167 
02168 QString KDirLister::nameFilter() const
02169 {
02170   return d->nameFilter;
02171 }
02172 
02173 void KDirLister::setMimeFilter( const QStringList& mimeFilter )
02174 {
02175     if (d->settings.mimeFilter == mimeFilter)
02176         return;
02177 
02178     d->prepareForSettingsChange();
02179     if (mimeFilter.contains("application/octet-stream")) // all files
02180         d->settings.mimeFilter.clear();
02181     else
02182         d->settings.mimeFilter = mimeFilter;
02183 }
02184 
02185 void KDirLister::setMimeExcludeFilter( const QStringList& mimeExcludeFilter )
02186 {
02187     if (d->settings.mimeExcludeFilter == mimeExcludeFilter)
02188         return;
02189 
02190     d->prepareForSettingsChange();
02191     d->settings.mimeExcludeFilter = mimeExcludeFilter;
02192 }
02193 
02194 
02195 void KDirLister::clearMimeFilter()
02196 {
02197     d->prepareForSettingsChange();
02198     d->settings.mimeFilter.clear();
02199     d->settings.mimeExcludeFilter.clear();
02200 }
02201 
02202 QStringList KDirLister::mimeFilters() const
02203 {
02204   return d->settings.mimeFilter;
02205 }
02206 
02207 bool KDirLister::matchesFilter( const QString& name ) const
02208 {
02209     return doNameFilter(name, d->settings.lstFilters);
02210 }
02211 
02212 bool KDirLister::matchesMimeFilter( const QString& mime ) const
02213 {
02214     return doMimeFilter(mime, d->settings.mimeFilter) &&
02215         d->doMimeExcludeFilter(mime, d->settings.mimeExcludeFilter);
02216 }
02217 
02218 // ================ protected methods ================ //
02219 
02220 bool KDirLister::matchesFilter( const KFileItem& item ) const
02221 {
02222   Q_ASSERT( !item.isNull() );
02223 
02224   if ( item.text() == ".." )
02225     return false;
02226 
02227   if ( !d->settings.isShowingDotFiles && item.isHidden() )
02228     return false;
02229 
02230   if ( item.isDir() || d->settings.lstFilters.isEmpty() )
02231     return true;
02232 
02233   return matchesFilter( item.text() );
02234 }
02235 
02236 bool KDirLister::matchesMimeFilter( const KFileItem& item ) const
02237 {
02238     Q_ASSERT(!item.isNull());
02239     // Don't lose time determining the mimetype if there is no filter
02240     if (d->settings.mimeFilter.isEmpty() && d->settings.mimeExcludeFilter.isEmpty())
02241         return true;
02242     return matchesMimeFilter(item.mimetype());
02243 }
02244 
02245 bool KDirLister::doNameFilter( const QString& name, const QList<QRegExp>& filters ) const
02246 {
02247   for ( QList<QRegExp>::const_iterator it = filters.begin(); it != filters.end(); ++it )
02248     if ( (*it).exactMatch( name ) )
02249       return true;
02250 
02251   return false;
02252 }
02253 
02254 bool KDirLister::doMimeFilter( const QString& mime, const QStringList& filters ) const
02255 {
02256   if ( filters.isEmpty() )
02257     return true;
02258 
02259   const KMimeType::Ptr mimeptr = KMimeType::mimeType(mime);
02260   if ( !mimeptr )
02261     return false;
02262 
02263   //kDebug(7004) << "doMimeFilter: investigating: "<<mimeptr->name();
02264   QStringList::const_iterator it = filters.begin();
02265   for ( ; it != filters.end(); ++it )
02266     if ( mimeptr->is(*it) )
02267       return true;
02268     //else   kDebug(7004) << "doMimeFilter: compared without result to  "<<*it;
02269 
02270   return false;
02271 }
02272 
02273 bool KDirLister::Private::doMimeExcludeFilter( const QString& mime, const QStringList& filters ) const
02274 {
02275   if ( filters.isEmpty() )
02276     return true;
02277 
02278   QStringList::const_iterator it = filters.begin();
02279   for ( ; it != filters.end(); ++it )
02280     if ( (*it) == mime )
02281       return false;
02282 
02283   return true;
02284 }
02285 
02286 void KDirLister::handleError( KIO::Job *job )
02287 {
02288   if ( d->autoErrorHandling )
02289     job->uiDelegate()->showErrorMessage();
02290 }
02291 
02292 
02293 // ================= private methods ================= //
02294 
02295 void KDirLister::Private::addNewItem(const KUrl& directoryUrl, const KFileItem &item)
02296 {
02297     if (!isItemVisible(item))
02298         return; // No reason to continue... bailing out here prevents a mimetype scan.
02299 
02300     //kDebug(7004) << "in" << directoryUrl << "item:" << item.url();
02301 
02302   if ( m_parent->matchesMimeFilter( item ) )
02303   {
02304     if ( !lstNewItems )
02305     {
02306       lstNewItems = new NewItemsHash;
02307     }
02308 
02309     Q_ASSERT( !item.isNull() );
02310     (*lstNewItems)[directoryUrl].append( item );            // items not filtered
02311   }
02312   else
02313   {
02314     if ( !lstMimeFilteredItems ) {
02315       lstMimeFilteredItems = new KFileItemList;
02316     }
02317 
02318     Q_ASSERT( !item.isNull() );
02319     lstMimeFilteredItems->append( item );   // only filtered by mime
02320   }
02321 }
02322 
02323 void KDirLister::Private::addNewItems(const KUrl& directoryUrl, const KFileItemList& items)
02324 {
02325   // TODO: make this faster - test if we have a filter at all first
02326   // DF: was this profiled? The matchesFoo() functions should be fast, w/o filters...
02327   // Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good.
02328   KFileItemList::const_iterator kit = items.begin();
02329   const KFileItemList::const_iterator kend = items.end();
02330   for ( ; kit != kend; ++kit )
02331     addNewItem(directoryUrl, *kit);
02332 }
02333 
02334 void KDirLister::Private::addRefreshItem(const KUrl& directoryUrl, const KFileItem& oldItem, const KFileItem& item)
02335 {
02336     const bool refreshItemWasFiltered = !isItemVisible(oldItem) ||
02337                                         !m_parent->matchesMimeFilter(oldItem);
02338   if (isItemVisible(item) && m_parent->matchesMimeFilter(item)) {
02339     if ( refreshItemWasFiltered )
02340     {
02341       if ( !lstNewItems ) {
02342         lstNewItems = new NewItemsHash;
02343       }
02344 
02345       Q_ASSERT( !item.isNull() );
02346       (*lstNewItems)[directoryUrl].append( item );
02347     }
02348     else
02349     {
02350       if ( !lstRefreshItems ) {
02351         lstRefreshItems = new QList<QPair<KFileItem,KFileItem> >;
02352       }
02353 
02354       Q_ASSERT( !item.isNull() );
02355       lstRefreshItems->append( qMakePair(oldItem, item) );
02356     }
02357   }
02358   else if ( !refreshItemWasFiltered )
02359   {
02360     if ( !lstRemoveItems ) {
02361       lstRemoveItems = new KFileItemList;
02362     }
02363 
02364     // notify the user that the mimetype of a file changed that doesn't match
02365     // a filter or does match an exclude filter
02366     // This also happens when renaming foo to .foo and dot files are hidden (#174721)
02367     Q_ASSERT(!oldItem.isNull());
02368     lstRemoveItems->append(oldItem);
02369   }
02370 }
02371 
02372 void KDirLister::Private::emitItems()
02373 {
02374   NewItemsHash *tmpNew = lstNewItems;
02375   lstNewItems = 0;
02376 
02377   KFileItemList *tmpMime = lstMimeFilteredItems;
02378   lstMimeFilteredItems = 0;
02379 
02380   QList<QPair<KFileItem, KFileItem> > *tmpRefresh = lstRefreshItems;
02381   lstRefreshItems = 0;
02382 
02383   KFileItemList *tmpRemove = lstRemoveItems;
02384   lstRemoveItems = 0;
02385 
02386     if (tmpNew) {
02387         QHashIterator<KUrl, KFileItemList> it(*tmpNew);
02388         while (it.hasNext()) {
02389             it.next();
02390             emit m_parent->itemsAdded(it.key(), it.value());
02391             emit m_parent->newItems(it.value()); // compat
02392         }
02393         delete tmpNew;
02394     }
02395 
02396   if ( tmpMime )
02397   {
02398     emit m_parent->itemsFilteredByMime( *tmpMime );
02399     delete tmpMime;
02400   }
02401 
02402   if ( tmpRefresh )
02403   {
02404     emit m_parent->refreshItems( *tmpRefresh );
02405     delete tmpRefresh;
02406   }
02407 
02408   if ( tmpRemove )
02409   {
02410       emit m_parent->itemsDeleted( *tmpRemove );
02411       delete tmpRemove;
02412   }
02413 }
02414 
02415 bool KDirLister::Private::isItemVisible(const KFileItem& item) const
02416 {
02417     // Note that this doesn't include mime filters, because
02418     // of the itemsFilteredByMime signal. Filtered-by-mime items are
02419     // considered "visible", they are just visible via a different signal...
02420     return (!settings.dirOnlyMode || item.isDir())
02421         && m_parent->matchesFilter(item);
02422 }
02423 
02424 void KDirLister::Private::emitItemsDeleted(const KFileItemList &_items)
02425 {
02426     KFileItemList items = _items;
02427     QMutableListIterator<KFileItem> it(items);
02428     while (it.hasNext()) {
02429         const KFileItem& item = it.next();
02430         if (isItemVisible(item) && m_parent->matchesMimeFilter(item)) {
02431             // for compat
02432             emit m_parent->deleteItem(item);
02433         } else {
02434             it.remove();
02435         }
02436     }
02437     if (!items.isEmpty())
02438         emit m_parent->itemsDeleted(items);
02439 }
02440 
02441 // ================ private slots ================ //
02442 
02443 void KDirLister::Private::_k_slotInfoMessage( KJob *, const QString& message )
02444 {
02445   emit m_parent->infoMessage( message );
02446 }
02447 
02448 void KDirLister::Private::_k_slotPercent( KJob *job, unsigned long pcnt )
02449 {
02450   jobData[static_cast<KIO::ListJob *>(job)].percent = pcnt;
02451 
02452   int result = 0;
02453 
02454   KIO::filesize_t size = 0;
02455 
02456   QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
02457   while ( dataIt != jobData.end() )
02458   {
02459     result += (*dataIt).percent * (*dataIt).totalSize;
02460     size += (*dataIt).totalSize;
02461     ++dataIt;
02462   }
02463 
02464   if ( size != 0 )
02465     result /= size;
02466   else
02467     result = 100;
02468   emit m_parent->percent( result );
02469 }
02470 
02471 void KDirLister::Private::_k_slotTotalSize( KJob *job, qulonglong size )
02472 {
02473   jobData[static_cast<KIO::ListJob *>(job)].totalSize = size;
02474 
02475   KIO::filesize_t result = 0;
02476   QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
02477   while ( dataIt != jobData.end() )
02478   {
02479     result += (*dataIt).totalSize;
02480     ++dataIt;
02481   }
02482 
02483   emit m_parent->totalSize( result );
02484 }
02485 
02486 void KDirLister::Private::_k_slotProcessedSize( KJob *job, qulonglong size )
02487 {
02488   jobData[static_cast<KIO::ListJob *>(job)].processedSize = size;
02489 
02490   KIO::filesize_t result = 0;
02491   QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
02492   while ( dataIt != jobData.end() )
02493   {
02494     result += (*dataIt).processedSize;
02495     ++dataIt;
02496   }
02497 
02498   emit m_parent->processedSize( result );
02499 }
02500 
02501 void KDirLister::Private::_k_slotSpeed( KJob *job, unsigned long spd )
02502 {
02503   jobData[static_cast<KIO::ListJob *>(job)].speed = spd;
02504 
02505   int result = 0;
02506   QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
02507   while ( dataIt != jobData.end() )
02508   {
02509     result += (*dataIt).speed;
02510     ++dataIt;
02511   }
02512 
02513   emit m_parent->speed( result );
02514 }
02515 
02516 uint KDirLister::Private::numJobs()
02517 {
02518 #ifdef DEBUG_CACHE
02519     // This code helps detecting stale entries in the jobData map.
02520     qDebug() << m_parent << "numJobs:" << jobData.count();
02521     QMapIterator<KIO::ListJob *, JobData> it(jobData);
02522     while (it.hasNext()) {
02523         it.next();
02524         qDebug() << (void*)it.key();
02525         qDebug() << it.key();
02526     }
02527 #endif
02528 
02529   return jobData.count();
02530 }
02531 
02532 void KDirLister::Private::jobDone( KIO::ListJob *job )
02533 {
02534   jobData.remove( job );
02535 }
02536 
02537 void KDirLister::Private::jobStarted( KIO::ListJob *job )
02538 {
02539   Private::JobData data;
02540   data.speed = 0;
02541   data.percent = 0;
02542   data.processedSize = 0;
02543   data.totalSize = 0;
02544 
02545   jobData.insert( job, data );
02546   complete = false;
02547 }
02548 
02549 void KDirLister::Private::connectJob( KIO::ListJob *job )
02550 {
02551   m_parent->connect( job, SIGNAL(infoMessage( KJob *, const QString&, const QString& )),
02552                      m_parent, SLOT(_k_slotInfoMessage( KJob *, const QString& )) );
02553   m_parent->connect( job, SIGNAL(percent( KJob *, unsigned long )),
02554                      m_parent, SLOT(_k_slotPercent( KJob *, unsigned long )) );
02555   m_parent->connect( job, SIGNAL(totalSize( KJob *, qulonglong )),
02556                      m_parent, SLOT(_k_slotTotalSize( KJob *, qulonglong )) );
02557   m_parent->connect( job, SIGNAL(processedSize( KJob *, qulonglong )),
02558                      m_parent, SLOT(_k_slotProcessedSize( KJob *, qulonglong )) );
02559   m_parent->connect( job, SIGNAL(speed( KJob *, unsigned long )),
02560                      m_parent, SLOT(_k_slotSpeed( KJob *, unsigned long )) );
02561 }
02562 
02563 void KDirLister::setMainWindow( QWidget *window )
02564 {
02565   d->window = window;
02566 }
02567 
02568 QWidget *KDirLister::mainWindow()
02569 {
02570   return d->window;
02571 }
02572 
02573 KFileItemList KDirLister::items( WhichItems which ) const
02574 {
02575     return itemsForDir( url(), which );
02576 }
02577 
02578 KFileItemList KDirLister::itemsForDir( const KUrl& dir, WhichItems which ) const
02579 {
02580     KFileItemList *allItems = kDirListerCache->itemsForDir( dir );
02581     if ( !allItems )
02582         return KFileItemList();
02583 
02584     if ( which == AllItems )
02585         return *allItems;
02586     else // only items passing the filters
02587     {
02588         KFileItemList result;
02589         KFileItemList::const_iterator kit = allItems->constBegin();
02590         const KFileItemList::const_iterator kend = allItems->constEnd();
02591         for ( ; kit != kend; ++kit )
02592         {
02593             const KFileItem& item = *kit;
02594             if (d->isItemVisible(item) && matchesMimeFilter(item)) {
02595                 result.append(item);
02596             }
02597         }
02598         return result;
02599     }
02600 }
02601 
02602 bool KDirLister::delayedMimeTypes() const
02603 {
02604     return d->delayedMimeTypes;
02605 }
02606 
02607 void KDirLister::setDelayedMimeTypes( bool delayedMimeTypes )
02608 {
02609     d->delayedMimeTypes = delayedMimeTypes;
02610 }
02611 
02612 // called by KDirListerCache::slotRedirection
02613 void KDirLister::Private::redirect(const KUrl& oldUrl, const KUrl& newUrl, bool keepItems)
02614 {
02615     if ( url.equals( oldUrl, KUrl::CompareWithoutTrailingSlash ) ) {
02616         if (!keepItems)
02617             rootFileItem = KFileItem();
02618         url = newUrl;
02619     }
02620 
02621     const int idx = lstDirs.indexOf( oldUrl );
02622     if (idx == -1) {
02623         kWarning(7004) << "Unexpected redirection from" << oldUrl << "to" << newUrl
02624                        << "but this dirlister is currently listing/holding" << lstDirs;
02625     } else {
02626         lstDirs[ idx ] = newUrl;
02627     }
02628 
02629     if ( lstDirs.count() == 1 ) {
02630         if (!keepItems)
02631             emit m_parent->clear();
02632         emit m_parent->redirection( newUrl );
02633     } else {
02634         if (!keepItems)
02635             emit m_parent->clear( oldUrl );
02636     }
02637     emit m_parent->redirection( oldUrl, newUrl );
02638 }
02639 
02640 void KDirListerCacheDirectoryData::moveListersWithoutCachedItemsJob(const KUrl& url)
02641 {
02642     // Move dirlisters from listersCurrentlyListing to listersCurrentlyHolding,
02643     // but not those that are still waiting on a CachedItemsJob...
02644     // Unit-testing note:
02645     // Run kdirmodeltest in valgrind to hit the case where an update
02646     // is triggered while a lister has a CachedItemsJob (different timing...)
02647     QMutableListIterator<KDirLister *> lister_it(listersCurrentlyListing);
02648     while (lister_it.hasNext()) {
02649         KDirLister* kdl = lister_it.next();
02650         if (!kdl->d->m_cachedItemsJob || kdl->d->m_cachedItemsJob->url() != url) {
02651             // OK, move this lister from "currently listing" to "currently holding".
02652 
02653             // Huh? The KDirLister was present twice in listersCurrentlyListing, or was in both lists?
02654             Q_ASSERT(!listersCurrentlyHolding.contains(kdl));
02655             if (!listersCurrentlyHolding.contains(kdl)) {
02656                 listersCurrentlyHolding.append(kdl);
02657             }
02658             lister_it.remove();
02659         } else {
02660             //kDebug(7004) << "Not moving" << kdl << "to listersCurrentlyHolding because it still has job" << kdl->d->m_cachedItemsJob;
02661         }
02662     }
02663 }
02664 
02665 KFileItem KDirLister::cachedItemForUrl(const KUrl& url)
02666 {
02667     return kDirListerCache->itemForUrl(url);
02668 }
02669 
02670 #include "kdirlister.moc"
02671 #include "kdirlister_p.moc"

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal