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