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