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

Skip menu "kdelibs-4.9.5 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