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

KIO

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

kdelibs

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