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