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

KDE's Doxygen guidelines are available online.

KIO

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

kdelibs-4.8.3 API Reference

Skip menu "kdelibs-4.8.3 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal