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