fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / kio / kio / kdirlister.cpp
blob0f7017010553844970d63a850872b05c2edb11c7
1 /* This file is part of the KDE project
2 Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
3 2000 Carsten Pfeiffer <pfeiffer@kde.org>
4 2003-2005 David Faure <faure@kde.org>
5 2001-2006 Michael Brade <brade@kde.org>
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
23 #include "kdirlister.h"
24 #include "kdirlister_p.h"
26 #include <QtCore/QRegExp>
28 #include <kdebug.h>
29 #include <kde_file.h>
30 #include <klocale.h>
31 #include <kio/job.h>
32 #include <kio/jobuidelegate.h>
33 #include <kmessagebox.h>
34 #include <kglobal.h>
35 #include <kglobalsettings.h>
36 #include "kprotocolmanager.h"
37 #include "kmountpoint.h"
38 #include <sys/stat.h>
40 #include <assert.h>
41 #include <QFile>
43 // Enable this to get printDebug() called often, to see the contents of the cache
44 //#define DEBUG_CACHE
46 // Make really sure it doesn't get activated in the final build
47 #ifdef NDEBUG
48 #undef DEBUG_CACHE
49 #endif
51 K_GLOBAL_STATIC(KDirListerCache, kDirListerCache)
53 KDirListerCache::KDirListerCache()
54 : itemsCached( 10 ) // keep the last 10 directories around
56 //kDebug(7004);
58 connect( &pendingUpdateTimer, SIGNAL(timeout()), this, SLOT(processPendingUpdates()) );
59 pendingUpdateTimer.setSingleShot( true );
61 connect( KDirWatch::self(), SIGNAL( dirty( const QString& ) ),
62 this, SLOT( slotFileDirty( const QString& ) ) );
63 connect( KDirWatch::self(), SIGNAL( created( const QString& ) ),
64 this, SLOT( slotFileCreated( const QString& ) ) );
65 connect( KDirWatch::self(), SIGNAL( deleted( const QString& ) ),
66 this, SLOT( slotFileDeleted( const QString& ) ) );
68 kdirnotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this);
69 connect(kdirnotify, SIGNAL(FileRenamed(QString,QString)), SLOT(slotFileRenamed(QString,QString)));
70 connect(kdirnotify, SIGNAL(FilesAdded(QString)), SLOT(slotFilesAdded(QString)));
71 connect(kdirnotify, SIGNAL(FilesChanged(QStringList)), SLOT(slotFilesChanged(QStringList)));
72 connect(kdirnotify, SIGNAL(FilesRemoved(QStringList)), SLOT(slotFilesRemoved(QStringList)));
74 // The use of KUrl::url() in ~DirItem (sendSignal) crashes if the static for QRegExpEngine got deleted already,
75 // so we need to destroy the KDirListerCache before that.
76 qAddPostRoutine(kDirListerCache.destroy);
79 KDirListerCache::~KDirListerCache()
81 //kDebug(7004);
83 qDeleteAll(itemsInUse);
84 itemsInUse.clear();
86 itemsCached.clear();
87 directoryData.clear();
89 if ( KDirWatch::exists() )
90 KDirWatch::self()->disconnect( this );
93 // setting _reload to true will emit the old files and
94 // call updateDirectory
95 bool KDirListerCache::listDir( KDirLister *lister, const KUrl& _u,
96 bool _keep, bool _reload )
98 // like this we don't have to worry about trailing slashes any further
99 KUrl _url(_u);
100 _url.cleanPath(); // kill consecutive slashes
102 if (!_url.host().isEmpty() && KProtocolInfo::protocolClass(_url.protocol()) == ":local") {
103 // ":local" protocols ignore the hostname, so strip it out preventively - #160057
104 _url.setHost(QString());
105 if (_keep == false)
106 emit lister->redirection(_url);
109 _url.adjustPath(KUrl::RemoveTrailingSlash);
110 const QString urlStr = _url.url();
112 if (!validUrl(lister, _url)) {
113 kDebug(7004) << lister << "url=" << _url << "not a valid url";
114 return false;
117 #ifdef DEBUG_CACHE
118 printDebug();
119 #endif
120 //kDebug(7004) << lister << "url=" << _url << "keep=" << _keep << "reload=" << _reload;
122 if (!_keep) {
123 // stop any running jobs for lister
124 stop(lister, true /*silent*/);
126 // clear our internal list for lister
127 forgetDirs(lister);
129 lister->d->rootFileItem = KFileItem();
130 } else if (lister->d->lstDirs.contains(_url)) {
131 // stop the job listing _url for this lister
132 stop(lister, _url, true /*silent*/);
134 // remove the _url as well, it will be added in a couple of lines again!
135 // forgetDirs with three args does not do this
136 // TODO: think about moving this into forgetDirs
137 lister->d->lstDirs.removeAll(_url);
139 // clear _url for lister
140 forgetDirs(lister, _url, true);
142 if (lister->d->url == _url)
143 lister->d->rootFileItem = KFileItem();
146 lister->d->complete = false;
148 lister->d->lstDirs.append(_url);
150 if (lister->d->url.isEmpty() || !_keep) // set toplevel URL only if not set yet
151 lister->d->url = _url;
153 DirItem *itemU = itemsInUse.value(urlStr);
155 KDirListerCacheDirectoryData& dirData = directoryData[urlStr]; // find or insert
157 if (dirData.listersCurrentlyListing.isEmpty()) {
158 // if there is an update running for _url already we get into
159 // the following case - it will just be restarted by updateDirectory().
161 dirData.listersCurrentlyListing.append(lister);
163 DirItem *itemFromCache;
164 if (itemU || (!_reload && (itemFromCache = itemsCached.take(urlStr)) ) ) {
165 if (itemU) {
166 kDebug(7004) << "Entry already in use:" << _url;
167 // if _reload is set, then we'll emit cached items and then updateDirectory.
168 } else {
169 kDebug(7004) << "Entry in cache:" << _url;
170 itemFromCache->decAutoUpdate();
171 itemsInUse.insert(urlStr, itemFromCache);
172 itemU = itemFromCache;
175 emit lister->started(_url);
177 // List items from the cache in a delayed manner, just like things would happen
178 // if we were not using the cache.
179 new KDirLister::Private::CachedItemsJob(lister, itemU->lstItems, itemU->rootItem, _url, _reload);
181 } else {
182 // dir not in cache or _reload is true
183 if (_reload) {
184 kDebug(7004) << "Reloading directory:" << _url;
185 itemsCached.remove(urlStr);
186 } else {
187 kDebug(7004) << "Listing directory:" << _url;
190 itemU = new DirItem(_url);
191 itemsInUse.insert(urlStr, itemU);
193 // // we have a limit of MAX_JOBS_PER_LISTER concurrently running jobs
194 // if ( lister->d->numJobs() >= MAX_JOBS_PER_LISTER )
195 // {
196 // pendingUpdates.insert( _url );
197 // }
198 // else
200 KIO::ListJob* job = KIO::listDir(_url, KIO::HideProgressInfo);
201 runningListJobs.insert(job, KIO::UDSEntryList());
203 lister->d->jobStarted(job);
204 lister->d->connectJob(job);
206 if (lister->d->window)
207 job->ui()->setWindow(lister->d->window);
209 connect(job, SIGNAL(entries(KIO::Job *, KIO::UDSEntryList)),
210 this, SLOT(slotEntries(KIO::Job *, KIO::UDSEntryList)));
211 connect(job, SIGNAL(result(KJob *)),
212 this, SLOT(slotResult(KJob *)));
213 connect(job, SIGNAL(redirection(KIO::Job *,KUrl)),
214 this, SLOT(slotRedirection(KIO::Job *,KUrl)));
216 emit lister->started(_url);
219 } else {
221 kDebug(7004) << "Entry currently being listed:" << _url << "by" << dirData.listersCurrentlyListing;
222 #ifdef DEBUG_CACHE
223 printDebug();
224 #endif
226 emit lister->started( _url );
228 // Maybe listersCurrentlyListing/listersCurrentlyHolding should be QSets?
229 Q_ASSERT(!dirData.listersCurrentlyListing.contains(lister));
230 dirData.listersCurrentlyListing.append( lister );
232 KIO::ListJob *job = jobForUrl( urlStr );
233 // job will be 0 if we were listing from cache rather than listing from a kio job.
234 if( job ) {
235 lister->d->jobStarted( job );
236 lister->d->connectJob( job );
238 Q_ASSERT( itemU );
240 // List existing items in a delayed manner, just like things would happen
241 // if we were not using the cache.
242 //kDebug() << "Listing" << itemU->lstItems.count() << "cached items soon";
243 new KDirLister::Private::CachedItemsJob(lister, itemU->lstItems, itemU->rootItem, _url, _reload);
245 #ifdef DEBUG_CACHE
246 printDebug();
247 #endif
250 // automatic updating of directories
251 if (lister->d->autoUpdate)
252 itemU->incAutoUpdate();
254 return true;
257 void KDirLister::Private::CachedItemsJob::done()
259 //kDebug() << "lister" << m_lister << "says" << m_lister->d->m_cachedItemsJob << "this=" << this;
260 Q_ASSERT(m_lister->d->m_cachedItemsJob == this);
261 kDirListerCache->emitItemsFromCache(m_lister, m_items, m_rootItem, m_url, m_reload, m_emitCompleted);
262 emitResult();
265 void KDirListerCache::emitItemsFromCache(KDirLister* lister, const KFileItemList& items, const KFileItem& rootItem, const KUrl& _url, bool _reload, bool _emitCompleted)
267 lister->d->m_cachedItemsJob = 0;
269 const QString urlStr = _url.url();
270 DirItem *itemU = kDirListerCache->itemsInUse.value(urlStr);
271 Q_ASSERT(itemU); // hey we're listing that dir, so this can't be 0, right?
273 KDirLister::Private* kdl = lister->d;
275 kdl->complete = false;
277 if ( kdl->rootFileItem.isNull() && kdl->url == _url )
278 kdl->rootFileItem = rootItem;
280 //kDebug(7004) << "emitting" << items.count() << "for lister" << lister;
281 kdl->addNewItems(_url, items);
282 kdl->emitItems();
284 KDirListerCacheDirectoryData& dirData = directoryData[urlStr];
285 Q_ASSERT(dirData.listersCurrentlyListing.contains(lister));
287 // Emit completed, unless we were told not to,
288 // or if listDir() was called while another directory listing for this dir was happening,
289 // so we "joined" it. We detect that using jobForUrl to ensure it's a real ListJob,
290 // not just a lister-specific CachedItemsJob (which wouldn't emit completed for us).
291 if (_emitCompleted && jobForUrl( urlStr ) == 0) {
293 Q_ASSERT(!dirData.listersCurrentlyHolding.contains(lister));
294 dirData.listersCurrentlyHolding.append( lister );
295 dirData.listersCurrentlyListing.removeAll( lister );
297 kdl->complete = true;
298 emit lister->completed( _url );
299 emit lister->completed();
301 if ( _reload || !itemU->complete ) {
302 updateDirectory( _url );
307 bool KDirListerCache::validUrl( const KDirLister *lister, const KUrl& url ) const
309 if ( !url.isValid() )
311 if ( lister->d->autoErrorHandling )
313 QString tmp = i18n("Malformed URL\n%1", url.prettyUrl() );
314 KMessageBox::error( lister->d->errorParent, tmp );
316 return false;
319 if ( !KProtocolManager::supportsListing( url ) )
321 if ( lister->d->autoErrorHandling )
323 QString tmp = i18n("URL cannot be listed\n%1", url.prettyUrl() );
324 KMessageBox::error( lister->d->errorParent, tmp );
326 return false;
329 return true;
332 void KDirListerCache::stop( KDirLister *lister, bool silent )
334 #ifdef DEBUG_CACHE
335 //printDebug();
336 #endif
337 //kDebug(7004) << "lister: " << lister;
338 bool stopped = false;
340 QHash<QString,KDirListerCacheDirectoryData>::iterator dirit = directoryData.begin();
341 const QHash<QString,KDirListerCacheDirectoryData>::iterator dirend = directoryData.end();
342 for( ; dirit != dirend ; ++dirit ) {
343 KDirListerCacheDirectoryData& dirData = dirit.value();
344 if ( dirData.listersCurrentlyListing.removeAll(lister) ) { // contains + removeAll in one go
345 // lister is listing url
346 const QString url = dirit.key();
348 //kDebug(7004) << " found lister in list - for " << url;
349 stopLister(lister, url, dirData, silent);
350 stopped = true;
354 if (lister->d->m_cachedItemsJob) {
355 delete lister->d->m_cachedItemsJob;
356 lister->d->m_cachedItemsJob = 0;
357 stopped = true;
360 if ( stopped ) {
361 if (!silent) {
362 emit lister->canceled();
364 lister->d->complete = true;
367 // this is wrong if there is still an update running!
368 //Q_ASSERT( lister->d->complete );
371 void KDirListerCache::stop(KDirLister *lister, const KUrl& _u, bool silent)
373 KUrl url(_u);
374 url.adjustPath( KUrl::RemoveTrailingSlash );
375 const QString urlStr = url.url();
377 if (lister->d->m_cachedItemsJob && lister->d->m_cachedItemsJob->url() == url) {
378 delete lister->d->m_cachedItemsJob;
379 lister->d->m_cachedItemsJob = 0;
382 // TODO: consider to stop all the "child jobs" of url as well
383 kDebug(7004) << lister << " url=" << url;
385 QHash<QString,KDirListerCacheDirectoryData>::iterator dirit = directoryData.find(urlStr);
386 if (dirit == directoryData.end())
387 return;
388 KDirListerCacheDirectoryData& dirData = dirit.value();
389 if ( dirData.listersCurrentlyListing.removeAll(lister) ) { // contains + removeAll in one go
391 stopLister(lister, urlStr, dirData, silent);
393 if ( lister->d->numJobs() == 0 ) {
394 lister->d->complete = true;
395 // we killed the last job for lister
396 if (!silent) {
397 emit lister->canceled();
403 // Helper for both stop() methods
404 void KDirListerCache::stopLister(KDirLister* lister, const QString& url, KDirListerCacheDirectoryData& dirData, bool silent)
406 // Let's just leave the job running.
407 // After all, update jobs do run for "listersCurrentlyHolding",
408 // so there's no reason to kill them just because @p lister is now a holder.
410 // Move lister to listersCurrentlyHolding
411 dirData.listersCurrentlyHolding.append(lister);
413 if (!silent)
414 emit lister->canceled(KUrl(url));
417 void KDirListerCache::setAutoUpdate( KDirLister *lister, bool enable )
419 // IMPORTANT: this method does not check for the current autoUpdate state!
421 for ( KUrl::List::const_iterator it = lister->d->lstDirs.constBegin();
422 it != lister->d->lstDirs.constEnd(); ++it ) {
423 DirItem* dirItem = itemsInUse.value((*it).url());
424 Q_ASSERT(dirItem);
425 if ( enable )
426 dirItem->incAutoUpdate();
427 else
428 dirItem->decAutoUpdate();
432 void KDirListerCache::forgetDirs( KDirLister *lister )
434 //kDebug(7004) << lister;
436 emit lister->clear();
437 // clear lister->d->lstDirs before calling forgetDirs(), so that
438 // it doesn't contain things that itemsInUse doesn't. When emitting
439 // the canceled signals, lstDirs must not contain anything that
440 // itemsInUse does not contain. (otherwise it might crash in findByName()).
441 const KUrl::List lstDirsCopy = lister->d->lstDirs;
442 lister->d->lstDirs.clear();
444 for ( KUrl::List::const_iterator it = lstDirsCopy.begin();
445 it != lstDirsCopy.end(); ++it ) {
446 forgetDirs( lister, *it, false );
450 static bool manually_mounted(const QString& path, const KMountPoint::List& possibleMountPoints)
452 KMountPoint::Ptr mp = possibleMountPoints.findByPath(path);
453 if (!mp) // not listed in fstab -> yes, manually mounted
454 return true;
455 const bool supermount = mp->mountType() == "supermount";
456 if (supermount) {
457 return true;
459 // noauto -> manually mounted. Otherwise, mounted at boot time, won't be unmounted any time soon hopefully.
460 return mp->mountOptions().contains("noauto");
464 void KDirListerCache::forgetDirs( KDirLister *lister, const KUrl& _url, bool notify )
466 //kDebug(7004) << lister << " _url: " << _url;
468 KUrl url( _url );
469 url.adjustPath( KUrl::RemoveTrailingSlash );
470 const QString urlStr = url.url();
472 DirectoryDataHash::iterator dit = directoryData.find(urlStr);
473 if (dit == directoryData.end())
474 return;
475 KDirListerCacheDirectoryData& dirData = *dit;
476 dirData.listersCurrentlyHolding.removeAll(lister);
478 // This lister doesn't care for updates running in <url> anymore
479 KIO::ListJob *job = jobForUrl(urlStr);
480 if (job)
481 lister->d->jobDone(job);
483 DirItem *item = itemsInUse.value(urlStr);
484 Q_ASSERT(item);
486 if ( dirData.listersCurrentlyHolding.isEmpty() && dirData.listersCurrentlyListing.isEmpty() ) {
487 // item not in use anymore -> move into cache if complete
488 directoryData.erase(dit);
489 itemsInUse.remove( urlStr );
491 // this job is a running update which nobody cares about anymore
492 if ( job ) {
493 killJob( job );
494 kDebug(7004) << "Killing update job for " << urlStr;
496 // Well, the user of KDirLister doesn't really care that we're stopping
497 // a background-running job from a previous URL (in listDir) -> commented out.
498 // stop() already emitted canceled.
499 //emit lister->canceled( url );
500 if ( lister->d->numJobs() == 0 ) {
501 lister->d->complete = true;
502 //emit lister->canceled();
506 if ( notify ) {
507 lister->d->lstDirs.removeAll( url );
508 emit lister->clear( url );
511 if ( item->complete ) {
512 kDebug(7004) << lister << " item moved into cache: " << url;
513 itemsCached.insert( urlStr, item );
515 const KMountPoint::List possibleMountPoints = KMountPoint::possibleMountPoints(KMountPoint::NeedMountOptions);
517 // Should we forget the dir for good, or keep a watch on it?
518 // Generally keep a watch, except when it would prevent
519 // unmounting a removable device (#37780)
520 const bool isLocal = item->url.isLocalFile();
521 bool isManuallyMounted = false;
522 bool containsManuallyMounted = false;
523 if (isLocal) {
524 isManuallyMounted = manually_mounted( item->url.toLocalFile(), possibleMountPoints );
525 if ( !isManuallyMounted ) {
526 // Look for a manually-mounted directory inside
527 // If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM
528 // I hope this isn't too slow
529 KFileItemList::const_iterator kit = item->lstItems.constBegin();
530 KFileItemList::const_iterator kend = item->lstItems.constEnd();
531 for ( ; kit != kend && !containsManuallyMounted; ++kit )
532 if ( (*kit).isDir() && manually_mounted((*kit).url().toLocalFile(), possibleMountPoints) )
533 containsManuallyMounted = true;
537 if ( isManuallyMounted || containsManuallyMounted )
539 kDebug(7004) << "Not adding a watch on " << item->url << " because it " <<
540 ( isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir" );
541 item->complete = false; // set to "dirty"
543 else
544 item->incAutoUpdate(); // keep watch
546 else
548 delete item;
549 item = 0;
553 if ( item && lister->d->autoUpdate )
554 item->decAutoUpdate();
557 void KDirListerCache::updateDirectory( const KUrl& _dir )
559 kDebug(7004) << _dir;
561 QString urlStr = _dir.url(KUrl::RemoveTrailingSlash);
562 if ( !checkUpdate( urlStr ) )
563 return;
565 // A job can be running to
566 // - only list a new directory: the listers are in listersCurrentlyListing
567 // - only update a directory: the listers are in listersCurrentlyHolding
568 // - update a currently running listing: the listers are in both
570 KDirListerCacheDirectoryData& dirData = directoryData[urlStr];
571 QList<KDirLister *> listers = dirData.listersCurrentlyListing;
572 QList<KDirLister *> holders = dirData.listersCurrentlyHolding;
574 // restart the job for _dir if it is running already
575 bool killed = false;
576 QWidget *window = 0;
577 KIO::ListJob *job = jobForUrl( urlStr );
578 if (job) {
579 window = job->ui()->window();
581 killJob( job );
582 killed = true;
584 foreach ( KDirLister *kdl, listers )
585 kdl->d->jobDone( job );
587 foreach ( KDirLister *kdl, holders )
588 kdl->d->jobDone( job );
589 } else {
590 // Emit any cached items.
591 // updateDirectory() is about the diff compared to the cached items...
592 Q_FOREACH(KDirLister *kdl, listers) {
593 if (kdl->d->m_cachedItemsJob) {
594 KDirLister::Private::CachedItemsJob* job = kdl->d->m_cachedItemsJob;
595 job->setEmitCompleted(false);
596 job->done(); // sets kdl->d->m_cachedItemsJob to 0
597 delete job;
598 killed = true;
602 //if (killed) {
603 // kDebug(7004) << "Killed=" << killed;
606 // we don't need to emit canceled signals since we only replaced the job,
607 // the listing is continuing.
609 if (!(listers.isEmpty() || killed)) {
610 kWarning() << "The unexpected happened.";
611 kWarning() << "listers=" << listers;
612 kWarning() << "job=" << job;
613 Q_FOREACH(KDirLister *kdl, listers) {
614 kDebug() << "lister" << kdl << "m_cachedItemsJob=" << kdl->d->m_cachedItemsJob;
616 #ifndef NDEBUG
617 printDebug();
618 #endif
620 Q_ASSERT( listers.isEmpty() || killed );
622 job = KIO::listDir( _dir, KIO::HideProgressInfo );
623 runningListJobs.insert( job, KIO::UDSEntryList() );
625 connect( job, SIGNAL(entries( KIO::Job *, const KIO::UDSEntryList & )),
626 this, SLOT(slotUpdateEntries( KIO::Job *, const KIO::UDSEntryList & )) );
627 connect( job, SIGNAL(result( KJob * )),
628 this, SLOT(slotUpdateResult( KJob * )) );
630 kDebug(7004) << "update started in" << _dir;
632 foreach ( KDirLister *kdl, listers ) {
633 kdl->d->jobStarted( job );
636 if ( !holders.isEmpty() ) {
637 if ( !killed ) {
638 bool first = true;
639 foreach ( KDirLister *kdl, holders ) {
640 kdl->d->jobStarted( job );
641 if ( first && kdl->d->window ) {
642 first = false;
643 job->ui()->setWindow( kdl->d->window );
645 emit kdl->started( _dir );
647 } else {
648 job->ui()->setWindow( window );
650 foreach ( KDirLister *kdl, holders ) {
651 kdl->d->jobStarted( job );
657 bool KDirListerCache::checkUpdate( const QString& _dir )
659 if ( !itemsInUse.contains(_dir) )
661 DirItem *item = itemsCached[_dir];
662 if ( item && item->complete )
664 item->complete = false;
665 item->decAutoUpdate();
666 // Hmm, this debug output might include login/password from the _dir URL.
667 //kDebug(7004) << "directory " << _dir << " not in use, marked dirty.";
669 //else
670 //kDebug(7004) << "aborted, directory " << _dir << " not in cache.";
672 return false;
674 else
675 return true;
678 KFileItem KDirListerCache::itemForUrl( const KUrl& url ) const
680 KFileItem *item = findByUrl( 0, url );
681 if (item) {
682 return *item;
683 } else {
684 return KFileItem();
688 KDirListerCache::DirItem *KDirListerCache::dirItemForUrl(const KUrl& dir) const
690 const QString urlStr = dir.url(KUrl::RemoveTrailingSlash);
691 DirItem *item = itemsInUse.value(urlStr);
692 if ( !item )
693 item = itemsCached[urlStr];
694 return item;
697 KFileItemList *KDirListerCache::itemsForDir(const KUrl& dir) const
699 DirItem *item = dirItemForUrl(dir);
700 return item ? &item->lstItems : 0;
703 KFileItem KDirListerCache::findByName( const KDirLister *lister, const QString& _name ) const
705 Q_ASSERT(lister);
707 for (KUrl::List::const_iterator it = lister->d->lstDirs.constBegin();
708 it != lister->d->lstDirs.constEnd(); ++it) {
709 DirItem* dirItem = itemsInUse.value((*it).url());
710 Q_ASSERT(dirItem);
711 const KFileItem item = dirItem->lstItems.findByName(_name);
712 if (!item.isNull())
713 return item;
716 return KFileItem();
719 KFileItem *KDirListerCache::findByUrl( const KDirLister *lister, const KUrl& _u ) const
721 KUrl url(_u);
722 url.adjustPath(KUrl::RemoveTrailingSlash);
724 // Maybe _u is a directory itself? (see KDirModelTest::testChmodDirectory)
725 DirItem* dirItem = dirItemForUrl(url);
726 if (dirItem && !dirItem->rootItem.isNull() && dirItem->rootItem.url() == url) {
727 // If lister is set, check that it contains this dir
728 if (!lister || lister->d->lstDirs.contains(url))
729 return &dirItem->rootItem;
732 KUrl parentDir(url);
733 parentDir.setPath( parentDir.directory() );
735 // If lister is set, check that it contains this dir
736 if (lister && !lister->d->lstDirs.contains(parentDir))
737 return 0;
739 dirItem = dirItemForUrl(parentDir);
740 if (dirItem) {
741 KFileItemList::iterator it = dirItem->lstItems.begin();
742 const KFileItemList::iterator end = dirItem->lstItems.end();
743 for (; it != end ; ++it) {
744 if ((*it).url() == url) {
745 return &*it;
750 return 0;
753 void KDirListerCache::slotFilesAdded( const QString &dir ) // from KDirNotify signals
755 kDebug(7004) << dir;
756 updateDirectory( KUrl(dir) );
759 void KDirListerCache::slotFilesRemoved( const QStringList &fileList ) // from KDirNotify signals
761 slotFilesRemoved(KUrl::List(fileList));
764 void KDirListerCache::slotFilesRemoved(const KUrl::List& fileList)
766 //kDebug(7004) << fileList.count();
767 // Group notifications by parent dirs (usually there would be only one parent dir)
768 QMap<QString, KFileItemList> removedItemsByDir;
769 KUrl::List deletedSubdirs;
771 for (KUrl::List::const_iterator it = fileList.begin(); it != fileList.end() ; ++it) {
772 const KUrl url(*it);
773 DirItem* dirItem = dirItemForUrl(url); // is it a listed directory?
774 if (dirItem) {
775 deletedSubdirs.append(url);
776 if (!dirItem->rootItem.isNull()) {
777 removedItemsByDir[url.url()].append(dirItem->rootItem);
781 KUrl parentDir(url);
782 parentDir.setPath(parentDir.directory());
783 dirItem = dirItemForUrl(parentDir);
784 if (!dirItem)
785 continue;
786 for (KFileItemList::iterator fit = dirItem->lstItems.begin(), fend = dirItem->lstItems.end(); fit != fend ; ++fit) {
787 if ((*fit).url() == url) {
788 const KFileItem fileitem = *fit;
789 removedItemsByDir[parentDir.url()].append(fileitem);
790 // If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case.
791 if (fileitem.isNull() || fileitem.isDir()) {
792 deletedSubdirs.append(url);
794 dirItem->lstItems.erase(fit); // remove fileitem from list
795 break;
800 QMap<QString, KFileItemList>::const_iterator rit = removedItemsByDir.constBegin();
801 for(; rit != removedItemsByDir.constEnd(); ++rit) {
802 // Tell the views about it before calling deleteDir.
803 // They might need the subdirs' file items (see the dirtree).
804 DirectoryDataHash::const_iterator dit = directoryData.constFind(rit.key());
805 if (dit != directoryData.constEnd()) {
806 itemsDeleted((*dit).listersCurrentlyHolding, rit.value());
810 Q_FOREACH(const KUrl& url, deletedSubdirs) {
811 // in case of a dir, check if we have any known children, there's much to do in that case
812 // (stopping jobs, removing dirs from cache etc.)
813 deleteDir(url);
817 void KDirListerCache::slotFilesChanged( const QStringList &fileList ) // from KDirNotify signals
819 //kDebug(7004) << fileList;
820 KUrl::List dirsToUpdate;
821 QStringList::const_iterator it = fileList.begin();
822 for (; it != fileList.end() ; ++it) {
823 KUrl url( *it );
824 KFileItem *fileitem = findByUrl(0, url);
825 if (!fileitem) {
826 kDebug(7004) << "item not found for" << url;
827 continue;
829 if (url.isLocalFile()) {
830 // we need to refresh the item, because e.g. the permissions can have changed.
831 aboutToRefreshItem(*fileitem);
832 KFileItem oldItem = *fileitem;
833 fileitem->refresh();
834 emitRefreshItem(oldItem, *fileitem);
835 } else {
836 pendingRemoteUpdates.insert(fileitem);
837 // For remote files, we won't be able to figure out the new information,
838 // we have to do a update (directory listing)
839 KUrl dir(url);
840 dir.setPath(dir.directory());
841 if (!dirsToUpdate.contains(dir))
842 dirsToUpdate.prepend(dir);
846 KUrl::List::const_iterator itdir = dirsToUpdate.constBegin();
847 for (; itdir != dirsToUpdate.constEnd() ; ++itdir)
848 updateDirectory( *itdir );
849 // ## TODO problems with current jobs listing/updating that dir
850 // ( see kde-2.2.2's kdirlister )
853 void KDirListerCache::slotFileRenamed( const QString &_src, const QString &_dst ) // from KDirNotify signals
855 KUrl src( _src );
856 KUrl dst( _dst );
857 kDebug(7004) << src << "->" << dst;
858 #ifdef DEBUG_CACHE
859 printDebug();
860 #endif
862 KUrl oldurl(src);
863 oldurl.adjustPath( KUrl::RemoveTrailingSlash );
864 KFileItem *fileitem = findByUrl(0, oldurl);
865 if (!fileitem) {
866 kDebug(7004) << "Item not found:" << oldurl;
867 return;
870 // Dest already exists? Was overwritten then (testcase: #151851)
871 // We better emit it as deleted -before- doing the renaming, otherwise
872 // the "update" mechanism will emit the old one as deleted and
873 // kdirmodel will delete the new (renamed) one!
874 KFileItem* existingDestItem = findByUrl(0, dst);
875 if (existingDestItem) {
876 //kDebug() << dst << "already existed, let's delete it";
877 slotFilesRemoved(dst);
880 // If the item had a UDS_URL as well as UDS_NAME set, the user probably wants
881 // to be updating the name only (since they can't see the URL).
882 // Check to see if a URL exists, and if so, if only the file part has changed,
883 // only update the name and not the underlying URL.
884 bool nameOnly = !fileitem->entry().stringValue( KIO::UDSEntry::UDS_URL ).isEmpty();
885 nameOnly &= src.directory( KUrl::IgnoreTrailingSlash | KUrl::AppendTrailingSlash ) ==
886 dst.directory( KUrl::IgnoreTrailingSlash | KUrl::AppendTrailingSlash );
888 if (!nameOnly && fileitem->isDir()) {
889 renameDir( src, dst );
890 // #172945 - if the fileitem was the root item of a DirItem that was just removed from the cache,
891 // then it's a dangling pointer now...
892 fileitem = findByUrl( 0, oldurl );
895 // Now update the KFileItem representing that file or dir (not exclusive with the above!)
896 if ( !fileitem->isLocalFile() && !fileitem->localPath().isEmpty() ) // it uses UDS_LOCAL_PATH? ouch, needs an update then
897 slotFilesChanged( QStringList() << src.url() );
898 else
900 aboutToRefreshItem( *fileitem );
901 const KFileItem oldItem = *fileitem;
902 if( nameOnly )
903 fileitem->setName( dst.fileName() );
904 else
905 fileitem->setUrl( dst );
906 fileitem->refreshMimeType();
907 fileitem->determineMimeType();
908 emitRefreshItem( oldItem, *fileitem );
911 #ifdef DEBUG_CACHE
912 printDebug();
913 #endif
916 void KDirListerCache::aboutToRefreshItem( const KFileItem& fileitem )
918 // Look whether this item was shown in any view, i.e. held by any dirlister
919 KUrl parentDir( fileitem.url() );
920 parentDir.setPath( parentDir.directory() );
921 const QString parentDirURL = parentDir.url();
923 DirectoryDataHash::iterator dit = directoryData.find(parentDirURL);
924 if (dit == directoryData.end())
925 return;
927 foreach (KDirLister *kdl, (*dit).listersCurrentlyHolding)
928 kdl->d->aboutToRefreshItem( fileitem );
930 // Also look in listersCurrentlyListing, in case the user manages to rename during a listing
931 foreach (KDirLister *kdl, (*dit).listersCurrentlyListing)
932 kdl->d->aboutToRefreshItem( fileitem );
935 void KDirListerCache::emitRefreshItem(const KFileItem& oldItem, const KFileItem& fileitem )
937 // Look whether this item was shown in any view, i.e. held by any dirlister
938 KUrl parentDir( oldItem.url() );
939 parentDir.setPath( parentDir.directory() );
940 QString parentDirURL = parentDir.url();
941 DirectoryDataHash::iterator dit = directoryData.find(parentDirURL);
942 QList<KDirLister *> listers;
943 // Also look in listersCurrentlyListing, in case the user manages to rename during a listing
944 if (dit != directoryData.end())
945 listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing;
946 if (oldItem.isDir()) {
947 // For a directory, look for dirlisters where it's the root item.
948 dit = directoryData.find(oldItem.url().url());
949 if (dit != directoryData.end())
950 listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing;
952 Q_FOREACH(KDirLister *kdl, listers) {
953 // For a directory, look for dirlisters where it's the root item.
954 if (oldItem.isDir() && kdl->d->rootFileItem == oldItem) {
955 kdl->d->rootFileItem = fileitem;
957 KUrl directoryUrl(oldItem.url());
958 directoryUrl.setPath(directoryUrl.directory());
959 kdl->d->addRefreshItem(directoryUrl, oldItem, fileitem);
960 kdl->d->emitItems();
964 // private slots
966 // Called by KDirWatch - usually when a dir we're watching has been modified,
967 // but it can also be called for a file.
968 void KDirListerCache::slotFileDirty( const QString& path )
970 kDebug(7004) << path;
971 // File or dir?
972 KDE_struct_stat buff;
973 if ( KDE::stat( path, &buff ) != 0 )
974 return; // error
975 const bool isDir = S_ISDIR(buff.st_mode);
976 KUrl url(path);
978 if (isDir) {
979 // A dir: launch an update job if anyone cares about it
980 updateDirectory(url);
981 } else {
982 // A file: do we know about it already?
983 KFileItem* existingItem = findByUrl(0, url);
984 if (!existingItem) {
985 // No - update the parent dir then
986 url.setPath(url.directory());
987 updateDirectory(url);
988 } else {
989 // A known file: delay updating it, FAM is flooding us with events
990 const QString urlStr = url.url(KUrl::RemoveTrailingSlash);
991 if (!pendingUpdates.contains(urlStr)) {
992 KUrl dir(url);
993 dir.setPath(dir.directory());
994 if (checkUpdate(dir.url())) {
995 pendingUpdates.insert(urlStr);
996 if (!pendingUpdateTimer.isActive())
997 pendingUpdateTimer.start( 500 );
1004 void KDirListerCache::slotFileCreated( const QString& path ) // from KDirWatch
1006 kDebug(7004) << path;
1007 // XXX: how to avoid a complete rescan here?
1008 KUrl u( path );
1009 u.setPath( u.directory() );
1010 updateDirectory( u );
1013 void KDirListerCache::slotFileDeleted( const QString& path ) // from KDirWatch
1015 kDebug(7004) << path;
1016 KUrl u( path );
1017 slotFilesRemoved( QStringList() << u.url() );
1020 void KDirListerCache::slotEntries( KIO::Job *job, const KIO::UDSEntryList &entries )
1022 KUrl url(joburl( static_cast<KIO::ListJob *>(job) ));
1023 url.adjustPath(KUrl::RemoveTrailingSlash);
1024 QString urlStr = url.url();
1026 //kDebug(7004) << "new entries for " << url;
1028 DirItem *dir = itemsInUse.value(urlStr);
1029 if (!dir) {
1030 kError(7004) << "Internal error: job is listing" << url << "but itemsInUse only knows about" << itemsInUse.keys();
1031 Q_ASSERT( dir );
1032 return;
1035 DirectoryDataHash::iterator dit = directoryData.find(urlStr);
1036 if (dit == directoryData.end()) {
1037 kError(7004) << "Internal error: job is listing" << url << "but directoryData doesn't know about that url, only about:" << directoryData.keys();
1038 Q_ASSERT(dit != directoryData.end());
1039 return;
1041 KDirListerCacheDirectoryData& dirData = *dit;
1042 Q_ASSERT( !dirData.listersCurrentlyListing.isEmpty() );
1044 // check if anyone wants the mimetypes immediately
1045 bool delayedMimeTypes = true;
1046 foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
1047 delayedMimeTypes &= kdl->d->delayedMimeTypes;
1049 KIO::UDSEntryList::const_iterator it = entries.begin();
1050 const KIO::UDSEntryList::const_iterator end = entries.end();
1051 for ( ; it != end; ++it )
1053 const QString name = (*it).stringValue( KIO::UDSEntry::UDS_NAME );
1055 Q_ASSERT( !name.isEmpty() );
1056 if ( name.isEmpty() )
1057 continue;
1059 if ( name == "." )
1061 Q_ASSERT( dir->rootItem.isNull() );
1062 // Try to reuse an existing KFileItem (if we listed the parent dir)
1063 // rather than creating a new one. There are many reasons:
1064 // 1) renames and permission changes to the item would have to emit the signals
1065 // twice, otherwise, so that both views manage to recognize the item.
1066 // 2) with kio_ftp we can only know that something is a symlink when
1067 // listing the parent, so prefer that item, which has more info.
1068 dir->rootItem = itemForUrl(url);
1069 if (dir->rootItem.isNull())
1070 dir->rootItem = KFileItem( *it, url, delayedMimeTypes, true );
1072 foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
1073 if ( kdl->d->rootFileItem.isNull() && kdl->d->url == url )
1074 kdl->d->rootFileItem = dir->rootItem;
1076 else if ( name != ".." )
1078 KFileItem item( *it, url, delayedMimeTypes, true );
1080 //kDebug(7004)<< "Adding item: " << item.url();
1081 dir->lstItems.append( item );
1083 foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
1084 kdl->d->addNewItem(url, item);
1088 foreach ( KDirLister *kdl, dirData.listersCurrentlyListing )
1089 kdl->d->emitItems();
1092 void KDirListerCache::slotResult( KJob *j )
1094 Q_ASSERT( j );
1095 KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
1096 runningListJobs.remove( job );
1098 KUrl jobUrl(joburl( job ));
1099 jobUrl.adjustPath(KUrl::RemoveTrailingSlash); // need remove trailing slashes again, in case of redirections
1100 QString jobUrlStr = jobUrl.url();
1102 kDebug(7004) << "finished listing" << jobUrl;
1103 #ifdef DEBUG_CACHE
1104 printDebug();
1105 #endif
1107 DirectoryDataHash::iterator dit = directoryData.find(jobUrlStr);
1108 Q_ASSERT(dit != directoryData.end());
1109 KDirListerCacheDirectoryData& dirData = *dit;
1110 Q_ASSERT( !dirData.listersCurrentlyListing.isEmpty() );
1111 QList<KDirLister *> listers = dirData.listersCurrentlyListing;
1113 // move all listers to the holding list, do it before emitting
1114 // the signals to make sure it exists in KDirListerCache in case someone
1115 // calls listDir during the signal emission
1116 Q_ASSERT( dirData.listersCurrentlyHolding.isEmpty() );
1117 dirData.moveListersWithoutCachedItemsJob();
1119 if ( job->error() )
1121 foreach ( KDirLister *kdl, listers )
1123 kdl->d->jobDone( job );
1124 kdl->handleError( job );
1125 emit kdl->canceled( jobUrl );
1126 if ( kdl->d->numJobs() == 0 )
1128 kdl->d->complete = true;
1129 emit kdl->canceled();
1133 else
1135 DirItem *dir = itemsInUse.value(jobUrlStr);
1136 Q_ASSERT( dir );
1137 dir->complete = true;
1139 foreach ( KDirLister* kdl, listers )
1141 kdl->d->jobDone( job );
1142 emit kdl->completed( jobUrl );
1143 if ( kdl->d->numJobs() == 0 )
1145 kdl->d->complete = true;
1146 emit kdl->completed();
1151 // TODO: hmm, if there was an error and job is a parent of one or more
1152 // of the pending urls we should cancel it/them as well
1153 processPendingUpdates();
1155 #ifdef DEBUG_CACHE
1156 printDebug();
1157 #endif
1160 void KDirListerCache::slotRedirection( KIO::Job *j, const KUrl& url )
1162 Q_ASSERT( j );
1163 KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
1165 KUrl oldUrl(job->url()); // here we really need the old url!
1166 KUrl newUrl(url);
1168 // strip trailing slashes
1169 oldUrl.adjustPath(KUrl::RemoveTrailingSlash);
1170 newUrl.adjustPath(KUrl::RemoveTrailingSlash);
1172 if ( oldUrl == newUrl ) {
1173 kDebug(7004) << "New redirection url same as old, giving up.";
1174 return;
1177 const QString oldUrlStr = oldUrl.url();
1178 const QString newUrlStr = newUrl.url();
1180 kDebug(7004) << oldUrl << "->" << newUrl;
1182 #ifdef DEBUG_CACHE
1183 printDebug();
1184 #endif
1186 // I don't think there can be dirItems that are children of oldUrl.
1187 // Am I wrong here? And even if so, we don't need to delete them, right?
1188 // DF: redirection happens before listDir emits any item. Makes little sense otherwise.
1190 // oldUrl cannot be in itemsCached because only completed items are moved there
1191 DirItem *dir = itemsInUse.take(oldUrlStr);
1192 Q_ASSERT( dir );
1194 DirectoryDataHash::iterator dit = directoryData.find(oldUrlStr);
1195 Q_ASSERT(dit != directoryData.end());
1196 KDirListerCacheDirectoryData oldDirData = *dit;
1197 directoryData.erase(dit);
1198 Q_ASSERT( !oldDirData.listersCurrentlyListing.isEmpty() );
1199 const QList<KDirLister *> listers = oldDirData.listersCurrentlyListing;
1200 Q_ASSERT( !listers.isEmpty() );
1202 foreach ( KDirLister *kdl, listers ) {
1203 kdl->d->redirect(oldUrlStr, newUrl, false /*clear items*/);
1206 // when a lister was stopped before the job emits the redirection signal, the old url will
1207 // also be in listersCurrentlyHolding
1208 const QList<KDirLister *> holders = oldDirData.listersCurrentlyHolding;
1209 foreach ( KDirLister *kdl, holders ) {
1210 kdl->d->jobStarted( job );
1211 // do it like when starting a new list-job that will redirect later
1212 // TODO: maybe don't emit started if there's an update running for newUrl already?
1213 emit kdl->started( oldUrl );
1215 kdl->d->redirect(oldUrl, newUrl, false /*clear items*/);
1218 DirItem *newDir = itemsInUse.value(newUrlStr);
1219 if ( newDir ) {
1220 kDebug(7004) << newUrl << "already in use";
1222 // only in this case there can newUrl already be in listersCurrentlyListing or listersCurrentlyHolding
1223 delete dir;
1225 // get the job if one's running for newUrl already (can be a list-job or an update-job), but
1226 // do not return this 'job', which would happen because of the use of redirectionURL()
1227 KIO::ListJob *oldJob = jobForUrl( newUrlStr, job );
1229 // listers of newUrl with oldJob: forget about the oldJob and use the already running one
1230 // which will be converted to an updateJob
1231 KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
1233 QList<KDirLister *>& curListers = newDirData.listersCurrentlyListing;
1234 if ( !curListers.isEmpty() ) {
1235 kDebug(7004) << "and it is currently listed";
1237 Q_ASSERT( oldJob ); // ?!
1239 foreach ( KDirLister *kdl, curListers ) { // listers of newUrl
1240 kdl->d->jobDone( oldJob );
1242 kdl->d->jobStarted( job );
1243 kdl->d->connectJob( job );
1246 // append listers of oldUrl with newJob to listers of newUrl with oldJob
1247 foreach ( KDirLister *kdl, listers )
1248 curListers.append( kdl );
1249 } else {
1250 curListers = listers;
1253 if ( oldJob ) // kill the old job, be it a list-job or an update-job
1254 killJob( oldJob );
1256 // holders of newUrl: use the already running job which will be converted to an updateJob
1257 QList<KDirLister *>& curHolders = newDirData.listersCurrentlyHolding;
1258 if ( !curHolders.isEmpty() ) {
1259 kDebug(7004) << "and it is currently held.";
1261 foreach ( KDirLister *kdl, curHolders ) { // holders of newUrl
1262 kdl->d->jobStarted( job );
1263 emit kdl->started( newUrl );
1266 // append holders of oldUrl to holders of newUrl
1267 foreach ( KDirLister *kdl, holders )
1268 curHolders.append( kdl );
1269 } else {
1270 curHolders = holders;
1274 // emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed
1275 // TODO: make this a separate method?
1276 foreach ( KDirLister *kdl, listers + holders ) {
1277 if ( kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl )
1278 kdl->d->rootFileItem = newDir->rootItem;
1280 kdl->d->addNewItems(newUrl, newDir->lstItems);
1281 kdl->d->emitItems();
1283 } else if ( (newDir = itemsCached.take( newUrlStr )) ) {
1284 kDebug(7004) << newUrl << "is unused, but already in the cache.";
1286 delete dir;
1287 itemsInUse.insert( newUrlStr, newDir );
1288 KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
1289 newDirData.listersCurrentlyListing = listers;
1290 newDirData.listersCurrentlyHolding = holders;
1292 // emit old items: listers, holders
1293 foreach ( KDirLister *kdl, listers + holders ) {
1294 if ( kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl )
1295 kdl->d->rootFileItem = newDir->rootItem;
1297 kdl->d->addNewItems(newUrl, newDir->lstItems);
1298 kdl->d->emitItems();
1300 } else {
1301 kDebug(7004) << newUrl << "has not been listed yet.";
1303 dir->rootItem = KFileItem();
1304 dir->lstItems.clear();
1305 dir->redirect( newUrl );
1306 itemsInUse.insert( newUrlStr, dir );
1307 KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
1308 newDirData.listersCurrentlyListing = listers;
1309 newDirData.listersCurrentlyHolding = holders;
1311 if ( holders.isEmpty() ) {
1312 #ifdef DEBUG_CACHE
1313 printDebug();
1314 #endif
1315 return; // only in this case the job doesn't need to be converted,
1319 // make the job an update job
1320 job->disconnect( this );
1322 connect( job, SIGNAL(entries( KIO::Job *, const KIO::UDSEntryList & )),
1323 this, SLOT(slotUpdateEntries( KIO::Job *, const KIO::UDSEntryList & )) );
1324 connect( job, SIGNAL(result( KJob * )),
1325 this, SLOT(slotUpdateResult( KJob * )) );
1327 // FIXME: autoUpdate-Counts!!
1329 #ifdef DEBUG_CACHE
1330 printDebug();
1331 #endif
1334 struct KDirListerCache::ItemInUseChange
1336 ItemInUseChange(const QString& old, const QString& newU, DirItem* di)
1337 : oldUrl(old), newUrl(newU), dirItem(di) {}
1338 QString oldUrl;
1339 QString newUrl;
1340 DirItem* dirItem;
1343 void KDirListerCache::renameDir( const KUrl &oldUrl, const KUrl &newUrl )
1345 kDebug(7004) << oldUrl << "->" << newUrl;
1346 const QString oldUrlStr = oldUrl.url(KUrl::RemoveTrailingSlash);
1347 const QString newUrlStr = newUrl.url(KUrl::RemoveTrailingSlash);
1349 // Not enough. Also need to look at any child dir, even sub-sub-sub-dir.
1350 //DirItem *dir = itemsInUse.take( oldUrlStr );
1351 //emitRedirections( oldUrl, url );
1353 QLinkedList<ItemInUseChange> itemsToChange;
1355 // Look at all dirs being listed/shown
1356 QHash<QString, DirItem *>::iterator itu = itemsInUse.begin();
1357 const QHash<QString, DirItem *>::iterator ituend = itemsInUse.end();
1358 for (; itu != ituend ; ++itu) {
1359 DirItem *dir = itu.value();
1360 KUrl oldDirUrl ( itu.key() );
1361 //kDebug(7004) << "itemInUse:" << oldDirUrl;
1362 // Check if this dir is oldUrl, or a subfolder of it
1363 if ( oldUrl.isParentOf( oldDirUrl ) ) {
1364 // TODO should use KUrl::cleanpath like isParentOf does
1365 QString relPath = oldDirUrl.path().mid( oldUrl.path().length() );
1367 KUrl newDirUrl( newUrl ); // take new base
1368 if ( !relPath.isEmpty() )
1369 newDirUrl.addPath( relPath ); // add unchanged relative path
1370 //kDebug(7004) << "new url=" << newDirUrl;
1372 // Update URL in dir item and in itemsInUse
1373 dir->redirect( newDirUrl );
1375 itemsToChange.append(ItemInUseChange(oldDirUrl.url(KUrl::RemoveTrailingSlash),
1376 newDirUrl.url(KUrl::RemoveTrailingSlash),
1377 dir));
1378 // Rename all items under that dir
1380 for ( KFileItemList::iterator kit = dir->lstItems.begin(), kend = dir->lstItems.end();
1381 kit != kend ; ++kit )
1383 aboutToRefreshItem(*kit);
1384 const KFileItem oldItem = *kit;
1386 const KUrl oldItemUrl ((*kit).url());
1387 const QString oldItemUrlStr( oldItemUrl.url(KUrl::RemoveTrailingSlash) );
1388 KUrl newItemUrl( oldItemUrl );
1389 newItemUrl.setPath( newDirUrl.path() );
1390 newItemUrl.addPath( oldItemUrl.fileName() );
1391 kDebug(7004) << "renaming" << oldItemUrl << "to" << newItemUrl;
1392 (*kit).setUrl(newItemUrl);
1394 emitRefreshItem(oldItem, *kit);
1396 emitRedirections( oldDirUrl, newDirUrl );
1400 // Do the changes to itemsInUse out of the loop to avoid messing up iterators,
1401 // and so that emitRefreshItem can find the stuff in the hash.
1402 foreach(const ItemInUseChange& i, itemsToChange) {
1403 itemsInUse.remove(i.oldUrl);
1404 itemsInUse.insert(i.newUrl, i.dirItem);
1407 // Is oldUrl a directory in the cache?
1408 // Remove any child of oldUrl from the cache - even if the renamed dir itself isn't in it!
1409 removeDirFromCache( oldUrl );
1410 // TODO rename, instead.
1413 // helper for renameDir, not used for redirections from KIO::listDir().
1414 void KDirListerCache::emitRedirections( const KUrl &oldUrl, const KUrl &newUrl )
1416 kDebug(7004) << oldUrl << "->" << newUrl;
1417 const QString oldUrlStr = oldUrl.url(KUrl::RemoveTrailingSlash);
1418 const QString newUrlStr = newUrl.url(KUrl::RemoveTrailingSlash);
1420 KIO::ListJob *job = jobForUrl( oldUrlStr );
1421 if ( job )
1422 killJob( job );
1424 // Check if we were listing this dir. Need to abort and restart with new name in that case.
1425 DirectoryDataHash::iterator dit = directoryData.find(oldUrlStr);
1426 if ( dit == directoryData.end() )
1427 return;
1428 const QList<KDirLister *> listers = (*dit).listersCurrentlyListing;
1429 const QList<KDirLister *> holders = (*dit).listersCurrentlyHolding;
1431 KDirListerCacheDirectoryData& newDirData = directoryData[newUrlStr];
1433 // Tell the world that the job listing the old url is dead.
1434 foreach ( KDirLister *kdl, listers ) {
1435 if ( job )
1436 kdl->d->jobDone( job );
1438 emit kdl->canceled( oldUrl );
1440 newDirData.listersCurrentlyListing += listers;
1442 // Check if we are currently displaying this directory (odds opposite wrt above)
1443 foreach ( KDirLister *kdl, holders ) {
1444 if ( job )
1445 kdl->d->jobDone( job );
1447 newDirData.listersCurrentlyHolding += holders;
1448 directoryData.erase(dit);
1450 if ( !listers.isEmpty() ) {
1451 updateDirectory( newUrl );
1453 // Tell the world about the new url
1454 foreach ( KDirLister *kdl, listers )
1455 emit kdl->started( newUrl );
1458 // And notify the dirlisters of the redirection
1459 foreach ( KDirLister *kdl, holders ) {
1460 kdl->d->redirect(oldUrl, newUrl, true /*keep items*/);
1464 void KDirListerCache::removeDirFromCache( const KUrl& dir )
1466 kDebug(7004) << dir;
1467 const QList<QString> cachedDirs = itemsCached.keys(); // seems slow, but there's no qcache iterator...
1468 foreach(const QString& cachedDir, cachedDirs) {
1469 if ( dir.isParentOf( KUrl( cachedDir ) ) )
1470 itemsCached.remove( cachedDir );
1474 void KDirListerCache::slotUpdateEntries( KIO::Job* job, const KIO::UDSEntryList& list )
1476 runningListJobs[static_cast<KIO::ListJob*>(job)] += list;
1479 void KDirListerCache::slotUpdateResult( KJob * j )
1481 Q_ASSERT( j );
1482 KIO::ListJob *job = static_cast<KIO::ListJob *>( j );
1484 KUrl jobUrl (joburl( job ));
1485 jobUrl.adjustPath(KUrl::RemoveTrailingSlash); // need remove trailing slashes again, in case of redirections
1486 QString jobUrlStr (jobUrl.url());
1488 kDebug(7004) << "finished update" << jobUrl;
1490 KDirListerCacheDirectoryData& dirData = directoryData[jobUrlStr];
1491 // Collect the dirlisters which were listing the URL using that ListJob
1492 // plus those that were already holding that URL - they all get updated.
1493 dirData.moveListersWithoutCachedItemsJob();
1494 QList<KDirLister *> listers = dirData.listersCurrentlyHolding;
1495 listers += dirData.listersCurrentlyListing;
1497 // once we are updating dirs that are only in the cache this will fail!
1498 Q_ASSERT( !listers.isEmpty() );
1500 if ( job->error() ) {
1501 foreach ( KDirLister* kdl, listers ) {
1502 kdl->d->jobDone( job );
1504 //don't bother the user
1505 //kdl->handleError( job );
1507 emit kdl->canceled( jobUrl );
1508 if ( kdl->d->numJobs() == 0 ) {
1509 kdl->d->complete = true;
1510 emit kdl->canceled();
1514 runningListJobs.remove( job );
1516 // TODO: if job is a parent of one or more
1517 // of the pending urls we should cancel them
1518 processPendingUpdates();
1519 return;
1522 DirItem *dir = itemsInUse.value(jobUrlStr, 0);
1523 Q_ASSERT(dir);
1524 dir->complete = true;
1527 // check if anyone wants the mimetypes immediately
1528 bool delayedMimeTypes = true;
1529 foreach ( KDirLister *kdl, listers )
1530 delayedMimeTypes &= kdl->d->delayedMimeTypes;
1532 QHash<QString, KFileItem*> fileItems; // fileName -> KFileItem*
1534 // Unmark all items in url
1535 for ( KFileItemList::iterator kit = dir->lstItems.begin(), kend = dir->lstItems.end() ; kit != kend ; ++kit )
1537 (*kit).unmark();
1538 fileItems.insert( (*kit).name(), &*kit );
1541 const KIO::UDSEntryList& buf = runningListJobs.value( job );
1542 KIO::UDSEntryList::const_iterator it = buf.constBegin();
1543 const KIO::UDSEntryList::const_iterator end = buf.constEnd();
1544 for ( ; it != end; ++it )
1546 // Form the complete url
1547 KFileItem item( *it, jobUrl, delayedMimeTypes, true );
1549 const QString name = item.name();
1550 Q_ASSERT( !name.isEmpty() );
1552 // we duplicate the check for dotdot here, to avoid iterating over
1553 // all items again and checking in matchesFilter() that way.
1554 if ( name.isEmpty() || name == ".." )
1555 continue;
1557 if ( name == "." )
1559 // if the update was started before finishing the original listing
1560 // there is no root item yet
1561 if ( dir->rootItem.isNull() )
1563 dir->rootItem = item;
1565 foreach ( KDirLister *kdl, listers )
1566 if ( kdl->d->rootFileItem.isNull() && kdl->d->url == jobUrl )
1567 kdl->d->rootFileItem = dir->rootItem;
1569 continue;
1572 // Find this item
1573 if (KFileItem* tmp = fileItems.value(item.name()))
1575 QSet<KFileItem*>::iterator pru_it = pendingRemoteUpdates.find(tmp);
1576 const bool inPendingRemoteUpdates = (pru_it != pendingRemoteUpdates.end());
1578 // check if something changed for this file, using KFileItem::cmp()
1579 if (!tmp->cmp( item ) || inPendingRemoteUpdates) {
1581 if (inPendingRemoteUpdates) {
1582 pendingRemoteUpdates.erase(pru_it);
1584 foreach ( KDirLister *kdl, listers )
1585 kdl->d->aboutToRefreshItem( *tmp );
1587 //kDebug(7004) << "file changed:" << tmp->name();
1589 const KFileItem oldItem = *tmp;
1590 *tmp = item;
1591 foreach ( KDirLister *kdl, listers )
1592 kdl->d->addRefreshItem(jobUrl, oldItem, *tmp);
1594 //kDebug(7004) << "marking" << tmp;
1595 tmp->mark();
1597 else // this is a new file
1599 //kDebug(7004) << "new file:" << name;
1601 KFileItem pitem(item);
1602 pitem.mark();
1603 dir->lstItems.append( pitem );
1605 foreach ( KDirLister *kdl, listers )
1606 kdl->d->addNewItem(jobUrl, pitem);
1610 runningListJobs.remove( job );
1612 deleteUnmarkedItems( listers, dir->lstItems );
1614 foreach ( KDirLister *kdl, listers ) {
1615 kdl->d->emitItems();
1617 kdl->d->jobDone( job );
1619 emit kdl->completed( jobUrl );
1620 if ( kdl->d->numJobs() == 0 )
1622 kdl->d->complete = true;
1623 emit kdl->completed();
1627 // TODO: hmm, if there was an error and job is a parent of one or more
1628 // of the pending urls we should cancel it/them as well
1629 processPendingUpdates();
1632 // private
1634 KIO::ListJob *KDirListerCache::jobForUrl( const QString& url, KIO::ListJob *not_job )
1636 QMap< KIO::ListJob *, KIO::UDSEntryList >::const_iterator it = runningListJobs.constBegin();
1637 while ( it != runningListJobs.constEnd() )
1639 KIO::ListJob *job = it.key();
1640 if ( joburl( job ).url(KUrl::RemoveTrailingSlash) == url && job != not_job )
1641 return job;
1642 ++it;
1644 return 0;
1647 const KUrl& KDirListerCache::joburl( KIO::ListJob *job )
1649 if ( job->redirectionUrl().isValid() )
1650 return job->redirectionUrl();
1651 else
1652 return job->url();
1655 void KDirListerCache::killJob( KIO::ListJob *job )
1657 runningListJobs.remove( job );
1658 job->disconnect( this );
1659 job->kill();
1662 void KDirListerCache::deleteUnmarkedItems( const QList<KDirLister *>& listers, KFileItemList &lstItems )
1664 KFileItemList deletedItems;
1665 // Find all unmarked items and delete them
1666 QMutableListIterator<KFileItem> kit(lstItems);
1667 while (kit.hasNext()) {
1668 const KFileItem& item = kit.next();
1669 if (!item.isMarked()) {
1670 //kDebug() << "deleted:" << item.name() << &item;
1671 deletedItems.append(item);
1672 kit.remove();
1675 if (!deletedItems.isEmpty())
1676 itemsDeleted(listers, deletedItems);
1679 void KDirListerCache::itemsDeleted(const QList<KDirLister *>& listers, const KFileItemList& deletedItems)
1681 Q_FOREACH(KDirLister *kdl, listers) {
1682 kdl->d->emitItemsDeleted(deletedItems);
1685 Q_FOREACH(const KFileItem& item, deletedItems) {
1686 if (item.isDir())
1687 deleteDir(item.url());
1691 void KDirListerCache::deleteDir( const KUrl& dirUrl )
1693 //kDebug() << dirUrl;
1694 // unregister and remove the children of the deleted item.
1695 // Idea: tell all the KDirListers that they should forget the dir
1696 // and then remove it from the cache.
1698 // Separate itemsInUse iteration and calls to forgetDirs (which modify itemsInUse)
1699 KUrl::List affectedItems;
1701 QHash<QString, DirItem *>::iterator itu = itemsInUse.begin();
1702 const QHash<QString, DirItem *>::iterator ituend = itemsInUse.end();
1703 for ( ; itu != ituend; ++itu ) {
1704 const KUrl deletedUrl( itu.key() );
1705 if ( dirUrl.isParentOf( deletedUrl ) ) {
1706 affectedItems.append(deletedUrl);
1710 foreach(const KUrl& deletedUrl, affectedItems) {
1711 const QString deletedUrlStr = deletedUrl.url();
1712 // stop all jobs for deletedUrlStr
1713 DirectoryDataHash::iterator dit = directoryData.find(deletedUrlStr);
1714 if (dit != directoryData.end()) {
1715 // we need a copy because stop modifies the list
1716 QList<KDirLister *> listers = (*dit).listersCurrentlyListing;
1717 foreach ( KDirLister *kdl, listers )
1718 stop( kdl, deletedUrl );
1719 // tell listers holding deletedUrl to forget about it
1720 // this will stop running updates for deletedUrl as well
1722 // we need a copy because forgetDirs modifies the list
1723 QList<KDirLister *> holders = (*dit).listersCurrentlyHolding;
1724 foreach ( KDirLister *kdl, holders ) {
1725 // lister's root is the deleted item
1726 if ( kdl->d->url == deletedUrl )
1728 // tell the view first. It might need the subdirs' items (which forgetDirs will delete)
1729 if ( !kdl->d->rootFileItem.isNull() ) {
1730 emit kdl->deleteItem( kdl->d->rootFileItem );
1731 emit kdl->itemsDeleted(KFileItemList() << kdl->d->rootFileItem);
1733 forgetDirs( kdl );
1734 kdl->d->rootFileItem = KFileItem();
1736 else
1738 const bool treeview = kdl->d->lstDirs.count() > 1;
1739 if ( !treeview )
1741 emit kdl->clear();
1742 kdl->d->lstDirs.clear();
1744 else
1745 kdl->d->lstDirs.removeAll( deletedUrl );
1747 forgetDirs( kdl, deletedUrl, treeview );
1752 // delete the entry for deletedUrl - should not be needed, it's in
1753 // items cached now
1754 int count = itemsInUse.remove( deletedUrlStr );
1755 Q_ASSERT( count == 0 );
1756 Q_UNUSED( count ); //keep gcc "unused variable" complaining quiet when in release mode
1759 // remove the children from the cache
1760 removeDirFromCache( dirUrl );
1763 // delayed updating of files, FAM is flooding us with events
1764 void KDirListerCache::processPendingUpdates()
1766 foreach(const QString& file, pendingUpdates) {
1767 kDebug(7004) << file;
1768 KUrl u(file);
1769 KFileItem *item = findByUrl( 0, u ); // search all items
1770 if ( item ) {
1771 // we need to refresh the item, because e.g. the permissions can have changed.
1772 aboutToRefreshItem( *item );
1773 KFileItem oldItem = *item;
1774 item->refresh();
1775 emitRefreshItem( oldItem, *item );
1778 pendingUpdates.clear();
1781 #ifndef NDEBUG
1782 void KDirListerCache::printDebug()
1784 kDebug(7004) << "Items in use:";
1785 QHash<QString, DirItem *>::const_iterator itu = itemsInUse.constBegin();
1786 const QHash<QString, DirItem *>::const_iterator ituend = itemsInUse.constEnd();
1787 for ( ; itu != ituend ; ++itu ) {
1788 kDebug(7004) << " " << itu.key() << "URL:" << itu.value()->url
1789 << "rootItem:" << ( !itu.value()->rootItem.isNull() ? itu.value()->rootItem.url() : KUrl() )
1790 << "autoUpdates refcount:" << itu.value()->autoUpdates
1791 << "complete:" << itu.value()->complete
1792 << QString("with %1 items.").arg(itu.value()->lstItems.count());
1795 kDebug(7004) << "Directory data:";
1796 DirectoryDataHash::const_iterator dit = directoryData.constBegin();
1797 for ( ; dit != directoryData.constEnd(); ++dit )
1799 QString list;
1800 foreach ( KDirLister* listit, (*dit).listersCurrentlyListing )
1801 list += " 0x" + QString::number( (qlonglong)listit, 16 );
1802 kDebug(7004) << " " << dit.key() << (*dit).listersCurrentlyListing.count() << "listers:" << list;
1803 foreach ( KDirLister* listit, (*dit).listersCurrentlyListing ) {
1804 if (listit->d->m_cachedItemsJob) {
1805 kDebug(7004) << " Lister" << listit << "has CachedItemsJob" << listit->d->m_cachedItemsJob;
1809 list.clear();
1810 foreach ( KDirLister* listit, (*dit).listersCurrentlyHolding )
1811 list += " 0x" + QString::number( (qlonglong)listit, 16 );
1812 kDebug(7004) << " " << dit.key() << (*dit).listersCurrentlyHolding.count() << "holders:" << list;
1815 QMap< KIO::ListJob *, KIO::UDSEntryList >::Iterator jit = runningListJobs.begin();
1816 kDebug(7004) << "Jobs:";
1817 for ( ; jit != runningListJobs.end() ; ++jit )
1818 kDebug(7004) << " " << jit.key() << "listing" << joburl( jit.key() ) << ":" << (*jit).count() << "entries.";
1820 kDebug(7004) << "Items in cache:";
1821 const QList<QString> cachedDirs = itemsCached.keys();
1822 foreach(const QString& cachedDir, cachedDirs) {
1823 DirItem* dirItem = itemsCached.object(cachedDir);
1824 kDebug(7004) << " " << cachedDir << "rootItem:"
1825 << (!dirItem->rootItem.isNull() ? dirItem->rootItem.url().prettyUrl() : QString("NULL") )
1826 << "with" << dirItem->lstItems.count() << "items.";
1829 #endif
1832 KDirLister::KDirLister( QObject* parent )
1833 : QObject(parent), d(new Private(this))
1835 //kDebug(7003) << "+KDirLister";
1837 d->complete = true;
1839 setAutoUpdate( true );
1840 setDirOnlyMode( false );
1841 setShowingDotFiles( false );
1843 setAutoErrorHandlingEnabled( true, 0 );
1846 KDirLister::~KDirLister()
1848 //kDebug(7003) << "-KDirLister";
1850 // Stop all running jobs
1851 if (!kDirListerCache.isDestroyed()) {
1852 stop();
1853 kDirListerCache->forgetDirs( this );
1856 delete d;
1859 bool KDirLister::openUrl( const KUrl& _url, OpenUrlFlags _flags )
1861 // emit the current changes made to avoid an inconsistent treeview
1862 if (d->hasPendingChanges && (_flags & Keep))
1863 emitChanges();
1865 d->hasPendingChanges = false;
1867 return kDirListerCache->listDir( this, _url, _flags & Keep, _flags & Reload );
1870 void KDirLister::stop()
1872 kDirListerCache->stop( this );
1875 void KDirLister::stop( const KUrl& _url )
1877 kDirListerCache->stop( this, _url );
1880 bool KDirLister::autoUpdate() const
1882 return d->autoUpdate;
1885 void KDirLister::setAutoUpdate( bool _enable )
1887 if ( d->autoUpdate == _enable )
1888 return;
1890 d->autoUpdate = _enable;
1891 kDirListerCache->setAutoUpdate( this, _enable );
1894 bool KDirLister::showingDotFiles() const
1896 return d->settings.isShowingDotFiles;
1899 void KDirLister::setShowingDotFiles( bool _showDotFiles )
1901 if ( d->settings.isShowingDotFiles == _showDotFiles )
1902 return;
1904 d->prepareForSettingsChange();
1905 d->settings.isShowingDotFiles = _showDotFiles;
1908 bool KDirLister::dirOnlyMode() const
1910 return d->settings.dirOnlyMode;
1913 void KDirLister::setDirOnlyMode( bool _dirsOnly )
1915 if ( d->settings.dirOnlyMode == _dirsOnly )
1916 return;
1918 d->prepareForSettingsChange();
1919 d->settings.dirOnlyMode = _dirsOnly;
1922 bool KDirLister::autoErrorHandlingEnabled() const
1924 return d->autoErrorHandling;
1927 void KDirLister::setAutoErrorHandlingEnabled( bool enable, QWidget* parent )
1929 d->autoErrorHandling = enable;
1930 d->errorParent = parent;
1933 KUrl KDirLister::url() const
1935 return d->url;
1938 KUrl::List KDirLister::directories() const
1940 return d->lstDirs;
1943 void KDirLister::emitChanges()
1945 d->emitChanges();
1948 void KDirLister::Private::emitChanges()
1950 if (!hasPendingChanges)
1951 return;
1953 // reset 'hasPendingChanges' now, in case of recursion
1954 // (testcase: enabling recursive scan in ktorrent, #174920)
1955 hasPendingChanges = false;
1957 const Private::FilterSettings newSettings = settings;
1958 settings = oldSettings; // temporarily
1960 // Mark all items that are currently visible
1961 Q_FOREACH(const KUrl& dir, lstDirs) {
1962 KFileItemList* itemList = kDirListerCache->itemsForDir(dir);
1963 KFileItemList::iterator kit = itemList->begin();
1964 const KFileItemList::iterator kend = itemList->end();
1965 for (; kit != kend; ++kit) {
1966 if (isItemVisible(*kit) && m_parent->matchesMimeFilter(*kit))
1967 (*kit).mark();
1968 else
1969 (*kit).unmark();
1973 settings = newSettings;
1975 Q_FOREACH(const KUrl& dir, lstDirs) {
1976 KFileItemList deletedItems;
1978 KFileItemList* itemList = kDirListerCache->itemsForDir(dir);
1979 KFileItemList::iterator kit = itemList->begin();
1980 const KFileItemList::iterator kend = itemList->end();
1981 for (; kit != kend; ++kit) {
1982 KFileItem& item = *kit;
1983 const QString text = item.text();
1984 if (text == "." || text == "..")
1985 continue;
1986 const bool nowVisible = isItemVisible(item) && m_parent->matchesMimeFilter(item);
1987 if (nowVisible && !item.isMarked())
1988 addNewItem(dir, item); // takes care of emitting newItem or itemsFilteredByMime
1989 else if (!nowVisible && item.isMarked())
1990 deletedItems.append(*kit);
1992 if (!deletedItems.isEmpty()) {
1993 emit m_parent->itemsDeleted(deletedItems);
1994 // for compat
1995 Q_FOREACH(const KFileItem& item, deletedItems)
1996 emit m_parent->deleteItem(item);
1998 emitItems();
2000 oldSettings = settings;
2003 void KDirLister::updateDirectory( const KUrl& _u )
2005 kDirListerCache->updateDirectory( _u );
2008 bool KDirLister::isFinished() const
2010 return d->complete;
2013 KFileItem KDirLister::rootItem() const
2015 return d->rootFileItem;
2018 KFileItem KDirLister::findByUrl( const KUrl& _url ) const
2020 KFileItem *item = kDirListerCache->findByUrl( this, _url );
2021 if (item) {
2022 return *item;
2023 } else {
2024 return KFileItem();
2028 KFileItem KDirLister::findByName( const QString& _name ) const
2030 return kDirListerCache->findByName( this, _name );
2034 // ================ public filter methods ================ //
2036 void KDirLister::setNameFilter( const QString& nameFilter )
2038 if (d->nameFilter == nameFilter)
2039 return;
2041 d->prepareForSettingsChange();
2043 d->settings.lstFilters.clear();
2044 d->nameFilter = nameFilter;
2045 // Split on white space
2046 const QStringList list = nameFilter.split( ' ', QString::SkipEmptyParts );
2047 for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
2048 d->settings.lstFilters.append(QRegExp(*it, Qt::CaseInsensitive, QRegExp::Wildcard));
2051 QString KDirLister::nameFilter() const
2053 return d->nameFilter;
2056 void KDirLister::setMimeFilter( const QStringList& mimeFilter )
2058 if (d->settings.mimeFilter == mimeFilter)
2059 return;
2061 d->prepareForSettingsChange();
2062 if (mimeFilter.contains("application/octet-stream")) // all files
2063 d->settings.mimeFilter.clear();
2064 else
2065 d->settings.mimeFilter = mimeFilter;
2068 void KDirLister::setMimeExcludeFilter( const QStringList& mimeExcludeFilter )
2070 if (d->settings.mimeExcludeFilter == mimeExcludeFilter)
2071 return;
2073 d->prepareForSettingsChange();
2074 d->settings.mimeExcludeFilter = mimeExcludeFilter;
2078 void KDirLister::clearMimeFilter()
2080 d->prepareForSettingsChange();
2081 d->settings.mimeFilter.clear();
2082 d->settings.mimeExcludeFilter.clear();
2085 QStringList KDirLister::mimeFilters() const
2087 return d->settings.mimeFilter;
2090 bool KDirLister::matchesFilter( const QString& name ) const
2092 return doNameFilter(name, d->settings.lstFilters);
2095 bool KDirLister::matchesMimeFilter( const QString& mime ) const
2097 return doMimeFilter(mime, d->settings.mimeFilter) &&
2098 d->doMimeExcludeFilter(mime, d->settings.mimeExcludeFilter);
2101 // ================ protected methods ================ //
2103 bool KDirLister::matchesFilter( const KFileItem& item ) const
2105 Q_ASSERT( !item.isNull() );
2107 if ( item.text() == ".." )
2108 return false;
2110 if ( !d->settings.isShowingDotFiles && item.isHidden() )
2111 return false;
2113 if ( item.isDir() || d->settings.lstFilters.isEmpty() )
2114 return true;
2116 return matchesFilter( item.text() );
2119 bool KDirLister::matchesMimeFilter( const KFileItem& item ) const
2121 Q_ASSERT(!item.isNull());
2122 // Don't lose time determining the mimetype if there is no filter
2123 if (d->settings.mimeFilter.isEmpty() && d->settings.mimeExcludeFilter.isEmpty())
2124 return true;
2125 return matchesMimeFilter(item.mimetype());
2128 bool KDirLister::doNameFilter( const QString& name, const QList<QRegExp>& filters ) const
2130 for ( QList<QRegExp>::const_iterator it = filters.begin(); it != filters.end(); ++it )
2131 if ( (*it).exactMatch( name ) )
2132 return true;
2134 return false;
2137 bool KDirLister::doMimeFilter( const QString& mime, const QStringList& filters ) const
2139 if ( filters.isEmpty() )
2140 return true;
2142 const KMimeType::Ptr mimeptr = KMimeType::mimeType(mime);
2143 if ( !mimeptr )
2144 return false;
2146 //kDebug(7004) << "doMimeFilter: investigating: "<<mimeptr->name();
2147 QStringList::const_iterator it = filters.begin();
2148 for ( ; it != filters.end(); ++it )
2149 if ( mimeptr->is(*it) )
2150 return true;
2151 //else kDebug(7004) << "doMimeFilter: compared without result to "<<*it;
2153 return false;
2156 bool KDirLister::Private::doMimeExcludeFilter( const QString& mime, const QStringList& filters ) const
2158 if ( filters.isEmpty() )
2159 return true;
2161 QStringList::const_iterator it = filters.begin();
2162 for ( ; it != filters.end(); ++it )
2163 if ( (*it) == mime )
2164 return false;
2166 return true;
2169 void KDirLister::handleError( KIO::Job *job )
2171 if ( d->autoErrorHandling )
2172 job->uiDelegate()->showErrorMessage();
2176 // ================= private methods ================= //
2178 void KDirLister::Private::addNewItem(const KUrl& directoryUrl, const KFileItem &item)
2180 if (!isItemVisible(item))
2181 return; // No reason to continue... bailing out here prevents a mimetype scan.
2183 if ( m_parent->matchesMimeFilter( item ) )
2185 if ( !lstNewItems )
2187 lstNewItems = new NewItemsHash;
2190 Q_ASSERT( !item.isNull() );
2191 (*lstNewItems)[directoryUrl].append( item ); // items not filtered
2193 else
2195 if ( !lstMimeFilteredItems ) {
2196 lstMimeFilteredItems = new KFileItemList;
2199 Q_ASSERT( !item.isNull() );
2200 lstMimeFilteredItems->append( item ); // only filtered by mime
2204 void KDirLister::Private::addNewItems(const KUrl& directoryUrl, const KFileItemList& items)
2206 // TODO: make this faster - test if we have a filter at all first
2207 // DF: was this profiled? The matchesFoo() functions should be fast, w/o filters...
2208 // Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good.
2209 KFileItemList::const_iterator kit = items.begin();
2210 const KFileItemList::const_iterator kend = items.end();
2211 for ( ; kit != kend; ++kit )
2212 addNewItem(directoryUrl, *kit);
2215 void KDirLister::Private::aboutToRefreshItem( const KFileItem &item )
2217 refreshItemWasFiltered = !isItemVisible(item) || !m_parent->matchesMimeFilter(item);
2220 void KDirLister::Private::addRefreshItem(const KUrl& directoryUrl, const KFileItem& oldItem, const KFileItem& item)
2222 if (isItemVisible(item) && m_parent->matchesMimeFilter(item)) {
2223 if ( refreshItemWasFiltered )
2225 if ( !lstNewItems ) {
2226 lstNewItems = new NewItemsHash;
2229 Q_ASSERT( !item.isNull() );
2230 (*lstNewItems)[directoryUrl].append( item );
2232 else
2234 if ( !lstRefreshItems ) {
2235 lstRefreshItems = new QList<QPair<KFileItem,KFileItem> >;
2238 Q_ASSERT( !item.isNull() );
2239 lstRefreshItems->append( qMakePair(oldItem, item) );
2242 else if ( !refreshItemWasFiltered )
2244 if ( !lstRemoveItems ) {
2245 lstRemoveItems = new KFileItemList;
2248 // notify the user that the mimetype of a file changed that doesn't match
2249 // a filter or does match an exclude filter
2250 // This also happens when renaming foo to .foo and dot files are hidden (#174721)
2251 Q_ASSERT(!oldItem.isNull());
2252 lstRemoveItems->append(oldItem);
2256 void KDirLister::Private::emitItems()
2258 NewItemsHash *tmpNew = lstNewItems;
2259 lstNewItems = 0;
2261 KFileItemList *tmpMime = lstMimeFilteredItems;
2262 lstMimeFilteredItems = 0;
2264 QList<QPair<KFileItem, KFileItem> > *tmpRefresh = lstRefreshItems;
2265 lstRefreshItems = 0;
2267 KFileItemList *tmpRemove = lstRemoveItems;
2268 lstRemoveItems = 0;
2270 if (tmpNew) {
2271 QHashIterator<KUrl, KFileItemList> it(*tmpNew);
2272 while (it.hasNext()) {
2273 it.next();
2274 emit m_parent->itemsAdded(it.key(), it.value());
2275 emit m_parent->newItems(it.value()); // compat
2277 delete tmpNew;
2280 if ( tmpMime )
2282 emit m_parent->itemsFilteredByMime( *tmpMime );
2283 delete tmpMime;
2286 if ( tmpRefresh )
2288 emit m_parent->refreshItems( *tmpRefresh );
2289 delete tmpRefresh;
2292 if ( tmpRemove )
2294 emit m_parent->itemsDeleted( *tmpRemove );
2295 delete tmpRemove;
2299 bool KDirLister::Private::isItemVisible(const KFileItem& item) const
2301 // Note that this doesn't include mime filters, because
2302 // of the itemsFilteredByMime signal. Filtered-by-mime items are
2303 // considered "visible", they are just visible via a different signal...
2304 return (!settings.dirOnlyMode || item.isDir())
2305 && m_parent->matchesFilter(item);
2308 void KDirLister::Private::emitItemsDeleted(const KFileItemList &_items)
2310 KFileItemList items = _items;
2311 QMutableListIterator<KFileItem> it(items);
2312 while (it.hasNext()) {
2313 const KFileItem& item = it.next();
2314 if (isItemVisible(item) && m_parent->matchesMimeFilter(item)) {
2315 // for compat
2316 emit m_parent->deleteItem(item);
2317 } else {
2318 it.remove();
2321 if (!items.isEmpty())
2322 emit m_parent->itemsDeleted(items);
2325 // ================ private slots ================ //
2327 void KDirLister::Private::_k_slotInfoMessage( KJob *, const QString& message )
2329 emit m_parent->infoMessage( message );
2332 void KDirLister::Private::_k_slotPercent( KJob *job, unsigned long pcnt )
2334 jobData[static_cast<KIO::ListJob *>(job)].percent = pcnt;
2336 int result = 0;
2338 KIO::filesize_t size = 0;
2340 QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
2341 while ( dataIt != jobData.end() )
2343 result += (*dataIt).percent * (*dataIt).totalSize;
2344 size += (*dataIt).totalSize;
2345 ++dataIt;
2348 if ( size != 0 )
2349 result /= size;
2350 else
2351 result = 100;
2352 emit m_parent->percent( result );
2355 void KDirLister::Private::_k_slotTotalSize( KJob *job, qulonglong size )
2357 jobData[static_cast<KIO::ListJob *>(job)].totalSize = size;
2359 KIO::filesize_t result = 0;
2360 QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
2361 while ( dataIt != jobData.end() )
2363 result += (*dataIt).totalSize;
2364 ++dataIt;
2367 emit m_parent->totalSize( result );
2370 void KDirLister::Private::_k_slotProcessedSize( KJob *job, qulonglong size )
2372 jobData[static_cast<KIO::ListJob *>(job)].processedSize = size;
2374 KIO::filesize_t result = 0;
2375 QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
2376 while ( dataIt != jobData.end() )
2378 result += (*dataIt).processedSize;
2379 ++dataIt;
2382 emit m_parent->processedSize( result );
2385 void KDirLister::Private::_k_slotSpeed( KJob *job, unsigned long spd )
2387 jobData[static_cast<KIO::ListJob *>(job)].speed = spd;
2389 int result = 0;
2390 QMap< KIO::ListJob *, Private::JobData >::Iterator dataIt = jobData.begin();
2391 while ( dataIt != jobData.end() )
2393 result += (*dataIt).speed;
2394 ++dataIt;
2397 emit m_parent->speed( result );
2400 uint KDirLister::Private::numJobs()
2402 #ifdef DEBUG_CACHE
2403 // This code helps detecting stale entries in the jobData map.
2404 qDebug() << m_parent << "numJobs:" << jobData.count();
2405 QMapIterator<KIO::ListJob *, JobData> it(jobData);
2406 while (it.hasNext()) {
2407 it.next();
2408 qDebug() << (void*)it.key();
2409 qDebug() << it.key();
2411 #endif
2413 return jobData.count();
2416 void KDirLister::Private::jobDone( KIO::ListJob *job )
2418 jobData.remove( job );
2421 void KDirLister::Private::jobStarted( KIO::ListJob *job )
2423 Private::JobData data;
2424 data.speed = 0;
2425 data.percent = 0;
2426 data.processedSize = 0;
2427 data.totalSize = 0;
2429 jobData.insert( job, data );
2430 complete = false;
2433 void KDirLister::Private::connectJob( KIO::ListJob *job )
2435 m_parent->connect( job, SIGNAL(infoMessage( KJob *, const QString&, const QString& )),
2436 m_parent, SLOT(_k_slotInfoMessage( KJob *, const QString& )) );
2437 m_parent->connect( job, SIGNAL(percent( KJob *, unsigned long )),
2438 m_parent, SLOT(_k_slotPercent( KJob *, unsigned long )) );
2439 m_parent->connect( job, SIGNAL(totalSize( KJob *, qulonglong )),
2440 m_parent, SLOT(_k_slotTotalSize( KJob *, qulonglong )) );
2441 m_parent->connect( job, SIGNAL(processedSize( KJob *, qulonglong )),
2442 m_parent, SLOT(_k_slotProcessedSize( KJob *, qulonglong )) );
2443 m_parent->connect( job, SIGNAL(speed( KJob *, unsigned long )),
2444 m_parent, SLOT(_k_slotSpeed( KJob *, unsigned long )) );
2447 void KDirLister::setMainWindow( QWidget *window )
2449 d->window = window;
2452 QWidget *KDirLister::mainWindow()
2454 return d->window;
2457 KFileItemList KDirLister::items( WhichItems which ) const
2459 return itemsForDir( url(), which );
2462 KFileItemList KDirLister::itemsForDir( const KUrl& dir, WhichItems which ) const
2464 KFileItemList *allItems = kDirListerCache->itemsForDir( dir );
2465 if ( !allItems )
2466 return KFileItemList();
2468 if ( which == AllItems )
2469 return *allItems;
2470 else // only items passing the filters
2472 KFileItemList result;
2473 KFileItemList::const_iterator kit = allItems->constBegin();
2474 const KFileItemList::const_iterator kend = allItems->constEnd();
2475 for ( ; kit != kend; ++kit )
2477 const KFileItem& item = *kit;
2478 if (d->isItemVisible(item) && matchesMimeFilter(item)) {
2479 result.append(item);
2482 return result;
2486 bool KDirLister::delayedMimeTypes() const
2488 return d->delayedMimeTypes;
2491 void KDirLister::setDelayedMimeTypes( bool delayedMimeTypes )
2493 d->delayedMimeTypes = delayedMimeTypes;
2496 // called by KDirListerCache::slotRedirection
2497 void KDirLister::Private::redirect(const KUrl& oldUrl, const KUrl& newUrl, bool keepItems)
2499 if ( url.equals( oldUrl, KUrl::CompareWithoutTrailingSlash ) ) {
2500 if (!keepItems)
2501 rootFileItem = KFileItem();
2502 url = newUrl;
2505 const int idx = lstDirs.indexOf( oldUrl );
2506 if (idx == -1) {
2507 kWarning(7004) << "Unexpected redirection from" << oldUrl << "to" << newUrl
2508 << "but this dirlister is currently listing/holding" << lstDirs;
2509 } else {
2510 lstDirs[ idx ] = newUrl;
2513 if ( lstDirs.count() == 1 ) {
2514 if (!keepItems)
2515 emit m_parent->clear();
2516 emit m_parent->redirection( newUrl );
2517 } else {
2518 if (!keepItems)
2519 emit m_parent->clear( oldUrl );
2521 emit m_parent->redirection( oldUrl, newUrl );
2524 void KDirListerCacheDirectoryData::moveListersWithoutCachedItemsJob()
2526 // Move dirlisters from listersCurrentlyListing to listersCurrentlyHolding,
2527 // but not those that are still waiting on a CachedItemsJob...
2528 // Unit-testing note:
2529 // Run kdirmodeltest in valgrind to hit the case where an update
2530 // is triggered while a lister has a CachedItemsJob (different timing...)
2531 QMutableListIterator<KDirLister *> lister_it(listersCurrentlyListing);
2532 while (lister_it.hasNext()) {
2533 KDirLister* kdl = lister_it.next();
2534 if (!kdl->d->m_cachedItemsJob) {
2535 // OK, move this lister from "currently listing" to "currently holding".
2537 // Huh? The KDirLister was present twice in listersCurrentlyListing, or was in both lists?
2538 Q_ASSERT(!listersCurrentlyHolding.contains(kdl));
2539 if (!listersCurrentlyHolding.contains(kdl)) {
2540 listersCurrentlyHolding.append(kdl);
2542 lister_it.remove();
2547 KFileItem KDirLister::cachedItemForUrl(const KUrl& url)
2549 return kDirListerCache->itemForUrl(url);
2552 #include "kdirlister.moc"
2553 #include "kdirlister_p.moc"