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>
32 #include <kio/jobuidelegate.h>
33 #include <kmessagebox.h>
35 #include <kglobalsettings.h>
36 #include "kprotocolmanager.h"
37 #include "kmountpoint.h"
43 // Enable this to get printDebug() called often, to see the contents of the cache
46 // Make really sure it doesn't get activated in the final build
51 K_GLOBAL_STATIC(KDirListerCache
, kDirListerCache
)
53 KDirListerCache::KDirListerCache()
54 : itemsCached( 10 ) // keep the last 10 directories around
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()
83 qDeleteAll(itemsInUse
);
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
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());
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";
120 //kDebug(7004) << lister << "url=" << _url << "keep=" << _keep << "reload=" << _reload;
123 // stop any running jobs for lister
124 stop(lister
, true /*silent*/);
126 // clear our internal list for 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
)) ) ) {
166 kDebug(7004) << "Entry already in use:" << _url
;
167 // if _reload is set, then we'll emit cached items and then updateDirectory.
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
);
182 // dir not in cache or _reload is true
184 kDebug(7004) << "Reloading directory:" << _url
;
185 itemsCached
.remove(urlStr
);
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 )
196 // pendingUpdates.insert( _url );
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
);
221 kDebug(7004) << "Entry currently being listed:" << _url
<< "by" << dirData
.listersCurrentlyListing
;
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.
235 lister
->d
->jobStarted( job
);
236 lister
->d
->connectJob( job
);
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
);
250 // automatic updating of directories
251 if (lister
->d
->autoUpdate
)
252 itemU
->incAutoUpdate();
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
);
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
);
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
);
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
);
332 void KDirListerCache::stop( KDirLister
*lister
, bool silent
)
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
);
354 if (lister
->d
->m_cachedItemsJob
) {
355 delete lister
->d
->m_cachedItemsJob
;
356 lister
->d
->m_cachedItemsJob
= 0;
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
)
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())
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
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
);
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());
426 dirItem
->incAutoUpdate();
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
455 const bool supermount
= mp
->mountType() == "supermount";
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;
469 url
.adjustPath( KUrl::RemoveTrailingSlash
);
470 const QString urlStr
= url
.url();
472 DirectoryDataHash::iterator dit
= directoryData
.find(urlStr
);
473 if (dit
== directoryData
.end())
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
);
481 lister
->d
->jobDone(job
);
483 DirItem
*item
= itemsInUse
.value(urlStr
);
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
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();
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;
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"
544 item
->incAutoUpdate(); // keep watch
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
) )
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
577 KIO::ListJob
*job
= jobForUrl( urlStr
);
579 window
= job
->ui()->window();
584 foreach ( KDirLister
*kdl
, listers
)
585 kdl
->d
->jobDone( job
);
587 foreach ( KDirLister
*kdl
, holders
)
588 kdl
->d
->jobDone( job
);
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
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
;
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() ) {
639 foreach ( KDirLister
*kdl
, holders
) {
640 kdl
->d
->jobStarted( job
);
641 if ( first
&& kdl
->d
->window
) {
643 job
->ui()->setWindow( kdl
->d
->window
);
645 emit kdl
->started( _dir
);
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.";
670 //kDebug(7004) << "aborted, directory " << _dir << " not in cache.";
678 KFileItem
KDirListerCache::itemForUrl( const KUrl
& url
) const
680 KFileItem
*item
= findByUrl( 0, url
);
688 KDirListerCache::DirItem
*KDirListerCache::dirItemForUrl(const KUrl
& dir
) const
690 const QString urlStr
= dir
.url(KUrl::RemoveTrailingSlash
);
691 DirItem
*item
= itemsInUse
.value(urlStr
);
693 item
= itemsCached
[urlStr
];
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
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());
711 const KFileItem item
= dirItem
->lstItems
.findByName(_name
);
719 KFileItem
*KDirListerCache::findByUrl( const KDirLister
*lister
, const KUrl
& _u
) const
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
;
733 parentDir
.setPath( parentDir
.directory() );
735 // If lister is set, check that it contains this dir
736 if (lister
&& !lister
->d
->lstDirs
.contains(parentDir
))
739 dirItem
= dirItemForUrl(parentDir
);
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
) {
753 void KDirListerCache::slotFilesAdded( const QString
&dir
) // from KDirNotify signals
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
) {
773 DirItem
* dirItem
= dirItemForUrl(url
); // is it a listed directory?
775 deletedSubdirs
.append(url
);
776 if (!dirItem
->rootItem
.isNull()) {
777 removedItemsByDir
[url
.url()].append(dirItem
->rootItem
);
782 parentDir
.setPath(parentDir
.directory());
783 dirItem
= dirItemForUrl(parentDir
);
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
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.)
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
) {
824 KFileItem
*fileitem
= findByUrl(0, url
);
826 kDebug(7004) << "item not found for" << url
;
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
;
834 emitRefreshItem(oldItem
, *fileitem
);
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)
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
857 kDebug(7004) << src
<< "->" << dst
;
863 oldurl
.adjustPath( KUrl::RemoveTrailingSlash
);
864 KFileItem
*fileitem
= findByUrl(0, oldurl
);
866 kDebug(7004) << "Item not found:" << oldurl
;
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() );
900 aboutToRefreshItem( *fileitem
);
901 const KFileItem oldItem
= *fileitem
;
903 fileitem
->setName( dst
.fileName() );
905 fileitem
->setUrl( dst
);
906 fileitem
->refreshMimeType();
907 fileitem
->determineMimeType();
908 emitRefreshItem( oldItem
, *fileitem
);
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())
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
);
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
;
972 KDE_struct_stat buff
;
973 if ( KDE::stat( path
, &buff
) != 0 )
975 const bool isDir
= S_ISDIR(buff
.st_mode
);
979 // A dir: launch an update job if anyone cares about it
980 updateDirectory(url
);
982 // A file: do we know about it already?
983 KFileItem
* existingItem
= findByUrl(0, url
);
985 // No - update the parent dir then
986 url
.setPath(url
.directory());
987 updateDirectory(url
);
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
)) {
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?
1009 u
.setPath( u
.directory() );
1010 updateDirectory( u
);
1013 void KDirListerCache::slotFileDeleted( const QString
& path
) // from KDirWatch
1015 kDebug(7004) << 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
);
1030 kError(7004) << "Internal error: job is listing" << url
<< "but itemsInUse only knows about" << itemsInUse
.keys();
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());
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() )
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
)
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
;
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();
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();
1135 DirItem
*dir
= itemsInUse
.value(jobUrlStr
);
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();
1160 void KDirListerCache::slotRedirection( KIO::Job
*j
, const KUrl
& url
)
1163 KIO::ListJob
*job
= static_cast<KIO::ListJob
*>( j
);
1165 KUrl
oldUrl(job
->url()); // here we really need the old 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.";
1177 const QString oldUrlStr
= oldUrl
.url();
1178 const QString newUrlStr
= newUrl
.url();
1180 kDebug(7004) << oldUrl
<< "->" << newUrl
;
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
);
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
);
1220 kDebug(7004) << newUrl
<< "already in use";
1222 // only in this case there can newUrl already be in listersCurrentlyListing or listersCurrentlyHolding
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
);
1250 curListers
= listers
;
1253 if ( oldJob
) // kill the old job, be it a list-job or an update-job
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
);
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.";
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();
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() ) {
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!!
1334 struct KDirListerCache::ItemInUseChange
1336 ItemInUseChange(const QString
& old
, const QString
& newU
, DirItem
* di
)
1337 : oldUrl(old
), newUrl(newU
), dirItem(di
) {}
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
),
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
);
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() )
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
) {
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
) {
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
)
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();
1522 DirItem
*dir
= itemsInUse
.value(jobUrlStr
, 0);
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
)
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
== ".." )
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
;
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
;
1591 foreach ( KDirLister
*kdl
, listers
)
1592 kdl
->d
->addRefreshItem(jobUrl
, oldItem
, *tmp
);
1594 //kDebug(7004) << "marking" << tmp;
1597 else // this is a new file
1599 //kDebug(7004) << "new file:" << name;
1601 KFileItem
pitem(item
);
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();
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
)
1647 const KUrl
& KDirListerCache::joburl( KIO::ListJob
*job
)
1649 if ( job
->redirectionUrl().isValid() )
1650 return job
->redirectionUrl();
1655 void KDirListerCache::killJob( KIO::ListJob
*job
)
1657 runningListJobs
.remove( job
);
1658 job
->disconnect( this );
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
);
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
) {
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
);
1734 kdl
->d
->rootFileItem
= KFileItem();
1738 const bool treeview
= kdl
->d
->lstDirs
.count() > 1;
1742 kdl
->d
->lstDirs
.clear();
1745 kdl
->d
->lstDirs
.removeAll( deletedUrl
);
1747 forgetDirs( kdl
, deletedUrl
, treeview
);
1752 // delete the entry for deletedUrl - should not be needed, it's in
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
;
1769 KFileItem
*item
= findByUrl( 0, u
); // search all items
1771 // we need to refresh the item, because e.g. the permissions can have changed.
1772 aboutToRefreshItem( *item
);
1773 KFileItem oldItem
= *item
;
1775 emitRefreshItem( oldItem
, *item
);
1778 pendingUpdates
.clear();
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
)
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
;
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.";
1832 KDirLister::KDirLister( QObject
* parent
)
1833 : QObject(parent
), d(new Private(this))
1835 //kDebug(7003) << "+KDirLister";
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()) {
1853 kDirListerCache
->forgetDirs( this );
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
))
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
)
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
)
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
)
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
1938 KUrl::List
KDirLister::directories() const
1943 void KDirLister::emitChanges()
1948 void KDirLister::Private::emitChanges()
1950 if (!hasPendingChanges
)
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
))
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
== "..")
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
);
1995 Q_FOREACH(const KFileItem
& item
, deletedItems
)
1996 emit m_parent
->deleteItem(item
);
2000 oldSettings
= settings
;
2003 void KDirLister::updateDirectory( const KUrl
& _u
)
2005 kDirListerCache
->updateDirectory( _u
);
2008 bool KDirLister::isFinished() const
2013 KFileItem
KDirLister::rootItem() const
2015 return d
->rootFileItem
;
2018 KFileItem
KDirLister::findByUrl( const KUrl
& _url
) const
2020 KFileItem
*item
= kDirListerCache
->findByUrl( this, _url
);
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
)
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
)
2061 d
->prepareForSettingsChange();
2062 if (mimeFilter
.contains("application/octet-stream")) // all files
2063 d
->settings
.mimeFilter
.clear();
2065 d
->settings
.mimeFilter
= mimeFilter
;
2068 void KDirLister::setMimeExcludeFilter( const QStringList
& mimeExcludeFilter
)
2070 if (d
->settings
.mimeExcludeFilter
== mimeExcludeFilter
)
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() == ".." )
2110 if ( !d
->settings
.isShowingDotFiles
&& item
.isHidden() )
2113 if ( item
.isDir() || d
->settings
.lstFilters
.isEmpty() )
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())
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
) )
2137 bool KDirLister::doMimeFilter( const QString
& mime
, const QStringList
& filters
) const
2139 if ( filters
.isEmpty() )
2142 const KMimeType::Ptr mimeptr
= KMimeType::mimeType(mime
);
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
) )
2151 //else kDebug(7004) << "doMimeFilter: compared without result to "<<*it;
2156 bool KDirLister::Private::doMimeExcludeFilter( const QString
& mime
, const QStringList
& filters
) const
2158 if ( filters
.isEmpty() )
2161 QStringList::const_iterator it
= filters
.begin();
2162 for ( ; it
!= filters
.end(); ++it
)
2163 if ( (*it
) == mime
)
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
) )
2187 lstNewItems
= new NewItemsHash
;
2190 Q_ASSERT( !item
.isNull() );
2191 (*lstNewItems
)[directoryUrl
].append( item
); // items not filtered
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
);
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
;
2261 KFileItemList
*tmpMime
= lstMimeFilteredItems
;
2262 lstMimeFilteredItems
= 0;
2264 QList
<QPair
<KFileItem
, KFileItem
> > *tmpRefresh
= lstRefreshItems
;
2265 lstRefreshItems
= 0;
2267 KFileItemList
*tmpRemove
= lstRemoveItems
;
2271 QHashIterator
<KUrl
, KFileItemList
> it(*tmpNew
);
2272 while (it
.hasNext()) {
2274 emit m_parent
->itemsAdded(it
.key(), it
.value());
2275 emit m_parent
->newItems(it
.value()); // compat
2282 emit m_parent
->itemsFilteredByMime( *tmpMime
);
2288 emit m_parent
->refreshItems( *tmpRefresh
);
2294 emit m_parent
->itemsDeleted( *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
)) {
2316 emit m_parent
->deleteItem(item
);
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
;
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
;
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
;
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
;
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
;
2390 QMap
< KIO::ListJob
*, Private::JobData
>::Iterator dataIt
= jobData
.begin();
2391 while ( dataIt
!= jobData
.end() )
2393 result
+= (*dataIt
).speed
;
2397 emit m_parent
->speed( result
);
2400 uint
KDirLister::Private::numJobs()
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()) {
2408 qDebug() << (void*)it
.key();
2409 qDebug() << it
.key();
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
;
2426 data
.processedSize
= 0;
2429 jobData
.insert( job
, data
);
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
)
2452 QWidget
*KDirLister::mainWindow()
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
);
2466 return KFileItemList();
2468 if ( which
== 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
);
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
) ) {
2501 rootFileItem
= KFileItem();
2505 const int idx
= lstDirs
.indexOf( oldUrl
);
2507 kWarning(7004) << "Unexpected redirection from" << oldUrl
<< "to" << newUrl
2508 << "but this dirlister is currently listing/holding" << lstDirs
;
2510 lstDirs
[ idx
] = newUrl
;
2513 if ( lstDirs
.count() == 1 ) {
2515 emit m_parent
->clear();
2516 emit m_parent
->redirection( newUrl
);
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
);
2547 KFileItem
KDirLister::cachedItemForUrl(const KUrl
& url
)
2549 return kDirListerCache
->itemForUrl(url
);
2552 #include "kdirlister.moc"
2553 #include "kdirlister_p.moc"