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