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