File indexing completed on 2024-05-05 16:13:15

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
0004     SPDX-FileCopyrightText: 2000 Carsten Pfeiffer <pfeiffer@kde.org>
0005     SPDX-FileCopyrightText: 2003-2005 David Faure <faure@kde.org>
0006     SPDX-FileCopyrightText: 2001-2006 Michael Brade <brade@kde.org>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "kcoredirlister.h"
0012 #include "kcoredirlister_p.h"
0013 
0014 #include "../utils_p.h"
0015 #include "kiocoredebug.h"
0016 #include "kmountpoint.h"
0017 #include "kprotocolmanager.h"
0018 #include <kio/listjob.h>
0019 
0020 #include <KJobUiDelegate>
0021 #include <KLocalizedString>
0022 
0023 #include <QDir>
0024 #include <QFile>
0025 #include <QFileInfo>
0026 #include <QMimeDatabase>
0027 #include <QRegularExpression>
0028 #include <QTextStream>
0029 
0030 #include <list>
0031 
0032 #include <QLoggingCategory>
0033 Q_DECLARE_LOGGING_CATEGORY(KIO_CORE_DIRLISTER)
0034 Q_LOGGING_CATEGORY(KIO_CORE_DIRLISTER, "kf.kio.core.dirlister", QtWarningMsg)
0035 
0036 // Enable this to get printDebug() called often, to see the contents of the cache
0037 // #define DEBUG_CACHE
0038 
0039 // Make really sure it doesn't get activated in the final build
0040 #ifdef NDEBUG
0041 #undef DEBUG_CACHE
0042 #endif
0043 
0044 Q_GLOBAL_STATIC(KCoreDirListerCache, kDirListerCache)
0045 
0046 KCoreDirListerCache::KCoreDirListerCache()
0047     : itemsCached(10)
0048     , // keep the last 10 directories around
0049     m_cacheHiddenFiles(10) // keep the last 10 ".hidden" files around
0050 {
0051     qCDebug(KIO_CORE_DIRLISTER);
0052 
0053     connect(&pendingUpdateTimer, &QTimer::timeout, this, &KCoreDirListerCache::processPendingUpdates);
0054     pendingUpdateTimer.setSingleShot(true);
0055 
0056     connect(KDirWatch::self(), &KDirWatch::dirty, this, &KCoreDirListerCache::slotFileDirty);
0057     connect(KDirWatch::self(), &KDirWatch::created, this, &KCoreDirListerCache::slotFileCreated);
0058     connect(KDirWatch::self(), &KDirWatch::deleted, this, &KCoreDirListerCache::slotFileDeleted);
0059 
0060 #ifndef KIO_ANDROID_STUB
0061     kdirnotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this);
0062     connect(kdirnotify, &org::kde::KDirNotify::FileRenamedWithLocalPath, this, &KCoreDirListerCache::slotFileRenamed);
0063     connect(kdirnotify, &org::kde::KDirNotify::FilesAdded, this, &KCoreDirListerCache::slotFilesAdded);
0064     connect(kdirnotify, &org::kde::KDirNotify::FilesChanged, this, &KCoreDirListerCache::slotFilesChanged);
0065     connect(kdirnotify, &org::kde::KDirNotify::FilesRemoved, this, qOverload<const QStringList &>(&KCoreDirListerCache::slotFilesRemoved));
0066 #endif
0067 }
0068 
0069 KCoreDirListerCache::~KCoreDirListerCache()
0070 {
0071     qCDebug(KIO_CORE_DIRLISTER);
0072 
0073     qDeleteAll(itemsInUse);
0074     itemsInUse.clear();
0075 
0076     itemsCached.clear();
0077     directoryData.clear();
0078     m_cacheHiddenFiles.clear();
0079 
0080     if (KDirWatch::exists()) {
0081         KDirWatch::self()->disconnect(this);
0082     }
0083 }
0084 
0085 // setting _reload to true will emit the old files and
0086 // call updateDirectory
0087 bool KCoreDirListerCache::listDir(KCoreDirLister *lister, const QUrl &dirUrl, bool _keep, bool _reload)
0088 {
0089     QUrl _url(dirUrl);
0090     _url.setPath(QDir::cleanPath(_url.path())); // kill consecutive slashes
0091 
0092     // like this we don't have to worry about trailing slashes any further
0093     _url = _url.adjusted(QUrl::StripTrailingSlash);
0094 
0095     QString resolved;
0096     if (_url.isLocalFile()) {
0097         // Resolve symlinks (#213799)
0098         const QString local = _url.toLocalFile();
0099         resolved = QFileInfo(local).canonicalFilePath();
0100         if (local != resolved) {
0101             canonicalUrls[QUrl::fromLocalFile(resolved)].append(_url);
0102         }
0103         // TODO: remove entry from canonicalUrls again in forgetDirs
0104         // Note: this is why we use a QStringList value in there rather than a std::set:
0105         // we can just remove one entry and not have to worry about other dirlisters
0106         // (the non-unicity of the stringlist gives us the refcounting, basically).
0107     }
0108 
0109     qCDebug(KIO_CORE_DIRLISTER) << lister << "url=" << _url << "keep=" << _keep << "reload=" << _reload;
0110 #ifdef DEBUG_CACHE
0111     printDebug();
0112 #endif
0113 
0114     if (!_keep) {
0115         // stop any running jobs for lister
0116         stop(lister, true /*silent*/);
0117 
0118         // clear our internal list for lister
0119         forgetDirs(lister);
0120 
0121         lister->d->rootFileItem = KFileItem();
0122     } else if (lister->d->lstDirs.contains(_url)) {
0123         // stop the job listing _url for this lister
0124         stopListingUrl(lister, _url, true /*silent*/);
0125 
0126         // remove the _url as well, it will be added in a couple of lines again!
0127         // forgetDirs with three args does not do this
0128         // TODO: think about moving this into forgetDirs
0129         lister->d->lstDirs.removeAll(_url);
0130 
0131         // clear _url for lister
0132         forgetDirs(lister, _url, true);
0133 
0134         if (lister->d->url == _url) {
0135             lister->d->rootFileItem = KFileItem();
0136         }
0137     }
0138 
0139     lister->d->complete = false;
0140 
0141     lister->d->lstDirs.append(_url);
0142 
0143     if (lister->d->url.isEmpty() || !_keep) { // set toplevel URL only if not set yet
0144         lister->d->url = _url;
0145     }
0146 
0147     DirItem *itemU = itemsInUse.value(_url);
0148 
0149     KCoreDirListerCacheDirectoryData &dirData = directoryData[_url]; // find or insert
0150 
0151     if (dirData.listersCurrentlyListing.isEmpty()) {
0152         // if there is an update running for _url already we get into
0153         // the following case - it will just be restarted by updateDirectory().
0154 
0155         dirData.listersCurrentlyListing.append(lister);
0156 
0157         DirItem *itemFromCache = nullptr;
0158         if (itemU || (!_reload && (itemFromCache = itemsCached.take(_url)))) {
0159             if (itemU) {
0160                 qCDebug(KIO_CORE_DIRLISTER) << "Entry already in use:" << _url;
0161                 // if _reload is set, then we'll emit cached items and then updateDirectory.
0162             } else {
0163                 qCDebug(KIO_CORE_DIRLISTER) << "Entry in cache:" << _url;
0164                 itemsInUse.insert(_url, itemFromCache);
0165                 itemU = itemFromCache;
0166             }
0167             if (lister->d->autoUpdate) {
0168                 itemU->incAutoUpdate();
0169             }
0170             if (itemFromCache && itemFromCache->watchedWhileInCache) {
0171                 // item is promoted from cache, update item autoupdate refcount accordingly
0172                 itemFromCache->watchedWhileInCache = false;
0173                 itemFromCache->decAutoUpdate();
0174             }
0175 
0176             Q_EMIT lister->started(_url);
0177 
0178             // List items from the cache in a delayed manner, just like things would happen
0179             // if we were not using the cache.
0180             new KCoreDirListerPrivate::CachedItemsJob(lister, _url, _reload);
0181 
0182         } else {
0183             // dir not in cache or _reload is true
0184             if (_reload) {
0185                 qCDebug(KIO_CORE_DIRLISTER) << "Reloading directory:" << _url;
0186                 itemsCached.remove(_url);
0187             } else {
0188                 qCDebug(KIO_CORE_DIRLISTER) << "Listing directory:" << _url;
0189             }
0190 
0191             itemU = new DirItem(_url, resolved);
0192             itemsInUse.insert(_url, itemU);
0193             if (lister->d->autoUpdate) {
0194                 itemU->incAutoUpdate();
0195             }
0196 
0197             KIO::ListJob *job = KIO::listDir(_url, KIO::HideProgressInfo);
0198             if (lister->requestMimeTypeWhileListing()) {
0199                 job->addMetaData(QStringLiteral("statDetails"), QString::number(KIO::StatDefaultDetails | KIO::StatMimeType));
0200             }
0201             runningListJobs.insert(job, KIO::UDSEntryList());
0202 
0203             lister->jobStarted(job);
0204             lister->d->connectJob(job);
0205 
0206             connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotEntries);
0207             connect(job, &KJob::result, this, &KCoreDirListerCache::slotResult);
0208             connect(job, &KIO::ListJob::redirection, this, &KCoreDirListerCache::slotRedirection);
0209 
0210             Q_EMIT lister->started(_url);
0211 
0212             qCDebug(KIO_CORE_DIRLISTER) << "Entry now being listed by" << dirData.listersCurrentlyListing;
0213         }
0214     } else {
0215         qCDebug(KIO_CORE_DIRLISTER) << "Entry currently being listed:" << _url << "by" << dirData.listersCurrentlyListing;
0216 #ifdef DEBUG_CACHE
0217         printDebug();
0218 #endif
0219 
0220         Q_EMIT lister->started(_url);
0221 
0222         // Maybe listersCurrentlyListing/listersCurrentlyHolding should be QSets?
0223         Q_ASSERT(!dirData.listersCurrentlyListing.contains(lister));
0224         dirData.listersCurrentlyListing.append(lister);
0225 
0226         // a new lister is listing this _url, incr watch refcount
0227         if (lister->d->autoUpdate) {
0228             itemU->incAutoUpdate();
0229         }
0230 
0231         KIO::ListJob *job = jobForUrl(_url);
0232         // job will be 0 if we were listing from cache rather than listing from a kio job.
0233         if (job) {
0234             lister->jobStarted(job);
0235             lister->d->connectJob(job);
0236         }
0237         Q_ASSERT(itemU);
0238 
0239         // List existing items in a delayed manner, just like things would happen
0240         // if we were not using the cache.
0241         qCDebug(KIO_CORE_DIRLISTER) << "Listing" << itemU->lstItems.count() << "cached items soon";
0242         auto *cachedItemsJob = new KCoreDirListerPrivate::CachedItemsJob(lister, _url, _reload);
0243         if (job) {
0244             // The ListJob will take care of emitting completed.
0245             // ### If it finishes before the CachedItemsJob, then we'll emit cached items after completed(), not sure how bad this is.
0246             cachedItemsJob->setEmitCompleted(false);
0247         }
0248 
0249 #ifdef DEBUG_CACHE
0250         printDebug();
0251 #endif
0252     }
0253 
0254     return true;
0255 }
0256 
0257 KCoreDirListerPrivate::CachedItemsJob *KCoreDirListerPrivate::cachedItemsJobForUrl(const QUrl &url) const
0258 {
0259     for (CachedItemsJob *job : m_cachedItemsJobs) {
0260         if (job->url() == url) {
0261             return job;
0262         }
0263     }
0264     return nullptr;
0265 }
0266 
0267 KCoreDirListerPrivate::CachedItemsJob::CachedItemsJob(KCoreDirLister *lister, const QUrl &url, bool reload)
0268     : KJob(lister)
0269     , m_lister(lister)
0270     , m_url(url)
0271     , m_reload(reload)
0272     , m_emitCompleted(true)
0273 {
0274     qCDebug(KIO_CORE_DIRLISTER) << "Creating CachedItemsJob" << this << "for lister" << lister << url;
0275     if (lister->d->cachedItemsJobForUrl(url)) {
0276         qCWarning(KIO_CORE) << "Lister" << lister << "has a cached items job already for" << url;
0277     }
0278     lister->d->m_cachedItemsJobs.append(this);
0279     setAutoDelete(true);
0280     start();
0281 }
0282 
0283 // Called by start() via QueuedConnection
0284 void KCoreDirListerPrivate::CachedItemsJob::done()
0285 {
0286     if (!m_lister) { // job was already killed, but waiting deletion due to deleteLater
0287         return;
0288     }
0289     kDirListerCache()->emitItemsFromCache(this, m_lister, m_url, m_reload, m_emitCompleted);
0290     emitResult();
0291 }
0292 
0293 bool KCoreDirListerPrivate::CachedItemsJob::doKill()
0294 {
0295     qCDebug(KIO_CORE_DIRLISTER) << this;
0296     kDirListerCache()->forgetCachedItemsJob(this, m_lister, m_url);
0297     if (!property("_kdlc_silent").toBool()) {
0298 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0299         Q_EMIT m_lister->canceled(m_url);
0300 #endif
0301         Q_EMIT m_lister->listingDirCanceled(m_url);
0302 
0303         Q_EMIT m_lister->canceled();
0304     }
0305     m_lister = nullptr;
0306     return true;
0307 }
0308 
0309 void KCoreDirListerCache::emitItemsFromCache(KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob,
0310                                              KCoreDirLister *lister,
0311                                              const QUrl &_url,
0312                                              bool _reload,
0313                                              bool _emitCompleted)
0314 {
0315     lister->d->complete = false;
0316 
0317     DirItem *itemU = kDirListerCache()->itemsInUse.value(_url);
0318     if (!itemU) {
0319         qCWarning(KIO_CORE) << "Can't find item for directory" << _url << "anymore";
0320     } else {
0321         const QList<KFileItem> items = itemU->lstItems;
0322         const KFileItem rootItem = itemU->rootItem;
0323         _reload = _reload || !itemU->complete;
0324 
0325         if (lister->d->rootFileItem.isNull() && !rootItem.isNull() && lister->d->url == _url) {
0326             lister->d->rootFileItem = rootItem;
0327         }
0328         if (!items.isEmpty()) {
0329             qCDebug(KIO_CORE_DIRLISTER) << "emitting" << items.count() << "for lister" << lister;
0330             lister->d->addNewItems(_url, items);
0331             lister->d->emitItems();
0332         }
0333     }
0334 
0335     forgetCachedItemsJob(cachedItemsJob, lister, _url);
0336 
0337     // Emit completed, unless we were told not to,
0338     // or if listDir() was called while another directory listing for this dir was happening,
0339     // so we "joined" it. We detect that using jobForUrl to ensure it's a real ListJob,
0340     // not just a lister-specific CachedItemsJob (which wouldn't emit completed for us).
0341     if (_emitCompleted) {
0342         lister->d->complete = true;
0343 
0344 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0345         Q_EMIT lister->completed(_url);
0346 #endif
0347         Q_EMIT lister->listingDirCompleted(_url);
0348         Q_EMIT lister->completed();
0349 
0350         if (_reload) {
0351             updateDirectory(_url);
0352         }
0353     }
0354 }
0355 
0356 void KCoreDirListerCache::forgetCachedItemsJob(KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob, KCoreDirLister *lister, const QUrl &_url)
0357 {
0358     // Modifications to data structures only below this point;
0359     // so that addNewItems is called with a consistent state
0360 
0361     lister->d->m_cachedItemsJobs.removeAll(cachedItemsJob);
0362 
0363     KCoreDirListerCacheDirectoryData &dirData = directoryData[_url];
0364     Q_ASSERT(dirData.listersCurrentlyListing.contains(lister));
0365 
0366     KIO::ListJob *listJob = jobForUrl(_url);
0367     if (!listJob) {
0368         Q_ASSERT(!dirData.listersCurrentlyHolding.contains(lister));
0369         qCDebug(KIO_CORE_DIRLISTER) << "Moving from listing to holding, because no more job" << lister << _url;
0370         dirData.listersCurrentlyHolding.append(lister);
0371         dirData.listersCurrentlyListing.removeAll(lister);
0372     } else {
0373         qCDebug(KIO_CORE_DIRLISTER) << "Still having a listjob" << listJob << ", so not moving to currently-holding.";
0374     }
0375 }
0376 
0377 void KCoreDirListerCache::stop(KCoreDirLister *lister, bool silent)
0378 {
0379     qCDebug(KIO_CORE_DIRLISTER) << "lister:" << lister << "silent=" << silent;
0380 
0381     const QList<QUrl> urls = lister->d->lstDirs;
0382     for (const QUrl &url : urls) {
0383         stopListingUrl(lister, url, silent);
0384     }
0385 }
0386 
0387 void KCoreDirListerCache::stopListingUrl(KCoreDirLister *lister, const QUrl &_u, bool silent)
0388 {
0389     QUrl url(_u);
0390     url = url.adjusted(QUrl::StripTrailingSlash);
0391 
0392     KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob = lister->d->cachedItemsJobForUrl(url);
0393     if (cachedItemsJob) {
0394         if (silent) {
0395             cachedItemsJob->setProperty("_kdlc_silent", true);
0396         }
0397         cachedItemsJob->kill(); // removes job from list, too
0398     }
0399 
0400     // TODO: consider to stop all the "child jobs" of url as well
0401     qCDebug(KIO_CORE_DIRLISTER) << lister << " url=" << url;
0402 
0403     const auto dirit = directoryData.find(url);
0404     if (dirit == directoryData.end()) {
0405         return;
0406     }
0407     KCoreDirListerCacheDirectoryData &dirData = dirit.value();
0408     if (dirData.listersCurrentlyListing.contains(lister)) {
0409         qCDebug(KIO_CORE_DIRLISTER) << " found lister" << lister << "in list - for" << url;
0410         if (dirData.listersCurrentlyListing.count() == 1) {
0411             // This was the only dirlister interested in the list job -> kill the job
0412             stopListJob(url, silent);
0413         } else {
0414             // Leave the job running for the other dirlisters, just unsubscribe us.
0415             dirData.listersCurrentlyListing.removeAll(lister);
0416             if (!silent) {
0417                 Q_EMIT lister->canceled();
0418 
0419 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0420                 Q_EMIT lister->canceled(url);
0421 #endif
0422                 Q_EMIT lister->listingDirCanceled(url);
0423             }
0424         }
0425     }
0426 }
0427 
0428 // Helper for stop() and stopListingUrl()
0429 void KCoreDirListerCache::stopListJob(const QUrl &url, bool silent)
0430 {
0431     // Old idea: if it's an update job, let's just leave the job running.
0432     // After all, update jobs do run for "listersCurrentlyHolding",
0433     // so there's no reason to kill them just because @p lister is now a holder.
0434 
0435     // However it could be a long-running non-local job (e.g. filenamesearch), which
0436     // the user wants to abort, and which will never be used for updating...
0437     // And in any case slotEntries/slotResult is not meant to be called by update jobs.
0438     // So, change of plan, let's kill it after all, in a way that triggers slotResult/slotUpdateResult.
0439 
0440     KIO::ListJob *job = jobForUrl(url);
0441     if (job) {
0442         qCDebug(KIO_CORE_DIRLISTER) << "Killing list job" << job << "for" << url;
0443         if (silent) {
0444             job->setProperty("_kdlc_silent", true);
0445         }
0446         job->kill(KJob::EmitResult);
0447     }
0448 }
0449 
0450 void KCoreDirListerCache::setAutoUpdate(KCoreDirLister *lister, bool enable)
0451 {
0452     // IMPORTANT: this method does not check for the current autoUpdate state!
0453 
0454     for (const QUrl &url : std::as_const(lister->d->lstDirs)) {
0455         DirItem *dirItem = itemsInUse.value(url);
0456         Q_ASSERT(dirItem);
0457         if (enable) {
0458             dirItem->incAutoUpdate();
0459         } else {
0460             dirItem->decAutoUpdate();
0461         }
0462     }
0463 }
0464 
0465 void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister)
0466 {
0467     qCDebug(KIO_CORE_DIRLISTER) << lister;
0468 
0469     Q_EMIT lister->clear();
0470     // clear lister->d->lstDirs before calling forgetDirs(), so that
0471     // it doesn't contain things that itemsInUse doesn't. When emitting
0472     // the canceled signals, lstDirs must not contain anything that
0473     // itemsInUse does not contain. (otherwise it might crash in findByName()).
0474     const QList<QUrl> lstDirsCopy = lister->d->lstDirs;
0475     lister->d->lstDirs.clear();
0476 
0477     qCDebug(KIO_CORE_DIRLISTER) << "Iterating over dirs" << lstDirsCopy;
0478     for (const QUrl &dir : lstDirsCopy) {
0479         forgetDirs(lister, dir, false);
0480     }
0481 }
0482 
0483 static bool manually_mounted(const QString &path, const KMountPoint::List &possibleMountPoints)
0484 {
0485     KMountPoint::Ptr mp = possibleMountPoints.findByPath(path);
0486     if (!mp) { // not listed in fstab -> yes, manually mounted
0487         if (possibleMountPoints.isEmpty()) { // no fstab at all -> don't assume anything
0488             return false;
0489         }
0490         return true;
0491     }
0492     // noauto -> manually mounted. Otherwise, mounted at boot time, won't be unmounted any time soon hopefully.
0493     return mp->mountOptions().contains(QLatin1String("noauto"));
0494 }
0495 
0496 void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister, const QUrl &_url, bool notify)
0497 {
0498     qCDebug(KIO_CORE_DIRLISTER) << lister << " _url: " << _url;
0499 
0500     const QUrl url = _url.adjusted(QUrl::StripTrailingSlash);
0501 
0502     DirectoryDataHash::iterator dit = directoryData.find(url);
0503     if (dit == directoryData.end()) {
0504         return;
0505     }
0506     KCoreDirListerCacheDirectoryData &dirData = *dit;
0507     dirData.listersCurrentlyHolding.removeAll(lister);
0508 
0509     // This lister doesn't care for updates running in <url> anymore
0510     KIO::ListJob *job = jobForUrl(url);
0511     if (job) {
0512         lister->d->jobDone(job);
0513     }
0514 
0515     DirItem *item = itemsInUse.value(url);
0516     Q_ASSERT(item);
0517     bool insertIntoCache = false;
0518 
0519     if (dirData.listersCurrentlyHolding.isEmpty() && dirData.listersCurrentlyListing.isEmpty()) {
0520         // item not in use anymore -> move into cache if complete
0521         directoryData.erase(dit);
0522         itemsInUse.remove(url);
0523 
0524         // this job is a running update which nobody cares about anymore
0525         if (job) {
0526             killJob(job);
0527             qCDebug(KIO_CORE_DIRLISTER) << "Killing update job for " << url;
0528 
0529             // Well, the user of KCoreDirLister doesn't really care that we're stopping
0530             // a background-running job from a previous URL (in listDir) -> commented out.
0531             // stop() already emitted canceled.
0532             // emit lister->canceled( url );
0533             if (lister->d->numJobs() == 0) {
0534                 lister->d->complete = true;
0535                 // emit lister->canceled();
0536             }
0537         }
0538 
0539         if (notify) {
0540             lister->d->lstDirs.removeAll(url);
0541 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0542             Q_EMIT lister->clear(url);
0543 #endif
0544             Q_EMIT lister->clearDir(url);
0545         }
0546 
0547         insertIntoCache = item->complete;
0548         if (insertIntoCache) {
0549             // TODO(afiestas): remove use of KMountPoint+manually_mounted and port to Solid:
0550             // 1) find Volume for the local path "item->url.toLocalFile()" (which could be anywhere
0551             // under the mount point) -- probably needs a new operator in libsolid query parser
0552             // 2) [**] becomes: if (Drive is hotpluggable or Volume is removable) "set to dirty" else "keep watch"
0553             const KMountPoint::List possibleMountPoints = KMountPoint::possibleMountPoints(KMountPoint::NeedMountOptions);
0554 
0555             // Should we forget the dir for good, or keep a watch on it?
0556             // Generally keep a watch, except when it would prevent
0557             // unmounting a removable device (#37780)
0558             const bool isLocal = item->url.isLocalFile();
0559             bool isManuallyMounted = false;
0560             bool containsManuallyMounted = false;
0561             if (isLocal) {
0562                 isManuallyMounted = manually_mounted(item->url.toLocalFile(), possibleMountPoints);
0563                 if (!isManuallyMounted) {
0564                     // Look for a manually-mounted directory inside
0565                     // If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM
0566                     // I hope this isn't too slow
0567                     auto kit = item->lstItems.constBegin();
0568                     const auto kend = item->lstItems.constEnd();
0569                     for (; kit != kend && !containsManuallyMounted; ++kit) {
0570                         if ((*kit).isDir() && manually_mounted((*kit).url().toLocalFile(), possibleMountPoints)) {
0571                             containsManuallyMounted = true;
0572                         }
0573                     }
0574                 }
0575             }
0576 
0577             if (isManuallyMounted || containsManuallyMounted) { // [**]
0578                 qCDebug(KIO_CORE_DIRLISTER) << "Not adding a watch on " << item->url << " because it "
0579                                             << (isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir");
0580                 item->complete = false; // set to "dirty"
0581             } else {
0582                 item->incAutoUpdate(); // keep watch, incr refcount to account for cache
0583                 item->watchedWhileInCache = true;
0584             }
0585         } else {
0586             delete item;
0587             item = nullptr;
0588         }
0589     }
0590 
0591     if (item && lister->d->autoUpdate) {
0592         item->decAutoUpdate();
0593     }
0594 
0595     // Inserting into QCache must be done last, since it might delete the item
0596     if (item && insertIntoCache) {
0597         qCDebug(KIO_CORE_DIRLISTER) << lister << "item moved into cache:" << url;
0598         itemsCached.insert(url, item);
0599     }
0600 }
0601 
0602 void KCoreDirListerCache::updateDirectory(const QUrl &_dir)
0603 {
0604     qCDebug(KIO_CORE_DIRLISTER) << _dir;
0605 
0606     QUrl dir = _dir.adjusted(QUrl::StripTrailingSlash);
0607     if (!checkUpdate(dir)) {
0608         auto parentDir = dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
0609         if (checkUpdate(parentDir)) {
0610             // if the parent is in use, update it instead
0611             dir = parentDir;
0612         } else {
0613             return;
0614         }
0615     }
0616 
0617     // A job can be running to
0618     //   - only list a new directory: the listers are in listersCurrentlyListing
0619     //   - only update a directory: the listers are in listersCurrentlyHolding
0620     //   - update a currently running listing: the listers are in both
0621 
0622     KCoreDirListerCacheDirectoryData &dirData = directoryData[dir];
0623     const QList<KCoreDirLister *> listers = dirData.listersCurrentlyListing;
0624     const QList<KCoreDirLister *> holders = dirData.listersCurrentlyHolding;
0625 
0626     qCDebug(KIO_CORE_DIRLISTER) << dir << "listers=" << listers << "holders=" << holders;
0627 
0628     bool killed = false;
0629     KIO::ListJob *job = jobForUrl(dir);
0630     if (job) {
0631         // the job is running already, tell it to do another update at the end
0632         // (don't kill it, we would keep doing that during a long download to a slow sshfs mount)
0633         job->setProperty("need_another_update", true);
0634         return;
0635     } else {
0636         // Emit any cached items.
0637         // updateDirectory() is about the diff compared to the cached items...
0638         for (const KCoreDirLister *kdl : listers) {
0639             KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob = kdl->d->cachedItemsJobForUrl(dir);
0640             if (cachedItemsJob) {
0641                 cachedItemsJob->setEmitCompleted(false);
0642                 cachedItemsJob->done(); // removes from cachedItemsJobs list
0643                 delete cachedItemsJob;
0644                 killed = true;
0645             }
0646         }
0647     }
0648     qCDebug(KIO_CORE_DIRLISTER) << "Killed=" << killed;
0649 
0650     // we don't need to emit canceled signals since we only replaced the job,
0651     // the listing is continuing.
0652 
0653     if (!(listers.isEmpty() || killed)) {
0654         qCWarning(KIO_CORE) << "The unexpected happened.";
0655         qCWarning(KIO_CORE) << "listers for" << dir << "=" << listers;
0656         qCWarning(KIO_CORE) << "job=" << job;
0657         for (const KCoreDirLister *kdl : listers) {
0658             qCDebug(KIO_CORE_DIRLISTER) << "lister" << kdl << "m_cachedItemsJobs=" << kdl->d->m_cachedItemsJobs;
0659         }
0660 #ifndef NDEBUG
0661         printDebug();
0662 #endif
0663     }
0664     Q_ASSERT(listers.isEmpty() || killed);
0665 
0666     job = KIO::listDir(dir, KIO::HideProgressInfo);
0667     runningListJobs.insert(job, KIO::UDSEntryList());
0668 
0669     const bool requestFromListers = std::any_of(listers.cbegin(), listers.cend(), [](KCoreDirLister *lister) {
0670         return lister->requestMimeTypeWhileListing();
0671     });
0672     const bool requestFromholders = std::any_of(holders.cbegin(), holders.cend(), [](KCoreDirLister *lister) {
0673         return lister->requestMimeTypeWhileListing();
0674     });
0675 
0676     if (requestFromListers || requestFromholders) {
0677         job->addMetaData(QStringLiteral("statDetails"), QString::number(KIO::StatDefaultDetails | KIO::StatMimeType));
0678     }
0679 
0680     connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotUpdateEntries);
0681     connect(job, &KJob::result, this, &KCoreDirListerCache::slotUpdateResult);
0682 
0683     qCDebug(KIO_CORE_DIRLISTER) << "update started in" << dir;
0684 
0685     for (KCoreDirLister *kdl : listers) {
0686         kdl->jobStarted(job);
0687     }
0688 
0689     if (!holders.isEmpty()) {
0690         if (!killed) {
0691             for (KCoreDirLister *kdl : holders) {
0692                 kdl->jobStarted(job);
0693                 Q_EMIT kdl->started(dir);
0694             }
0695         } else {
0696             for (KCoreDirLister *kdl : holders) {
0697                 kdl->jobStarted(job);
0698             }
0699         }
0700     }
0701 }
0702 
0703 bool KCoreDirListerCache::checkUpdate(const QUrl &_dir)
0704 {
0705     if (!itemsInUse.contains(_dir)) {
0706         DirItem *item = itemsCached[_dir];
0707         if (item && item->complete) {
0708             item->complete = false;
0709             item->watchedWhileInCache = false;
0710             item->decAutoUpdate();
0711             qCDebug(KIO_CORE_DIRLISTER) << "directory " << _dir << " not in use, marked dirty.";
0712         }
0713         // else
0714         qCDebug(KIO_CORE_DIRLISTER) << "aborted, directory " << _dir << " not in cache.";
0715         return false;
0716     } else {
0717         return true;
0718     }
0719 }
0720 
0721 KFileItem KCoreDirListerCache::itemForUrl(const QUrl &url) const
0722 {
0723     return findByUrl(nullptr, url);
0724 }
0725 
0726 KCoreDirListerCache::DirItem *KCoreDirListerCache::dirItemForUrl(const QUrl &dir) const
0727 {
0728     const QUrl url = dir.adjusted(QUrl::StripTrailingSlash);
0729     DirItem *item = itemsInUse.value(url);
0730     if (!item) {
0731         item = itemsCached[url];
0732     }
0733     return item;
0734 }
0735 
0736 QList<KFileItem> *KCoreDirListerCache::itemsForDir(const QUrl &dir) const
0737 {
0738     DirItem *item = dirItemForUrl(dir);
0739     return item ? &item->lstItems : nullptr;
0740 }
0741 
0742 KFileItem KCoreDirListerCache::findByName(const KCoreDirLister *lister, const QString &_name) const
0743 {
0744     Q_ASSERT(lister);
0745 
0746     auto isMatch = [&_name](const KFileItem &item) {
0747         return _name == item.name();
0748     };
0749 
0750     for (const auto &dirUrl : std::as_const(lister->d->lstDirs)) {
0751         DirItem *dirItem = itemsInUse.value(dirUrl);
0752         Q_ASSERT(dirItem);
0753 
0754         auto it = std::find_if(dirItem->lstItems.cbegin(), dirItem->lstItems.cend(), isMatch);
0755         if (it != dirItem->lstItems.cend()) {
0756             return *it;
0757         }
0758     }
0759 
0760     return {};
0761 }
0762 
0763 KFileItem KCoreDirListerCache::findByUrl(const KCoreDirLister *lister, const QUrl &_u) const
0764 {
0765     QUrl url(_u);
0766     url = url.adjusted(QUrl::StripTrailingSlash);
0767 
0768     const QUrl parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
0769     DirItem *dirItem = dirItemForUrl(parentDir);
0770     if (dirItem) {
0771         // If lister is set, check that it contains this dir
0772         if (!lister || lister->d->lstDirs.contains(parentDir)) {
0773             // Binary search
0774             auto it = std::lower_bound(dirItem->lstItems.begin(), dirItem->lstItems.end(), url);
0775             if (it != dirItem->lstItems.end() && it->url() == url) {
0776                 return *it;
0777             }
0778         }
0779     }
0780 
0781     // Maybe _u is a directory itself? (see KDirModelTest::testChmodDirectory)
0782     // We check this last, though, we prefer returning a kfileitem with an actual
0783     // name if possible (and we make it '.' for root items later).
0784     dirItem = dirItemForUrl(url);
0785     if (dirItem && !dirItem->rootItem.isNull() && dirItem->rootItem.url() == url) {
0786         // If lister is set, check that it contains this dir
0787         if (!lister || lister->d->lstDirs.contains(url)) {
0788             return dirItem->rootItem;
0789         }
0790     }
0791 
0792     return KFileItem();
0793 }
0794 
0795 void KCoreDirListerCache::slotFilesAdded(const QString &dir /*url*/) // from KDirNotify signals
0796 {
0797     QUrl urlDir(dir);
0798     itemsAddedInDirectory(urlDir);
0799 }
0800 
0801 void KCoreDirListerCache::itemsAddedInDirectory(const QUrl &urlDir)
0802 {
0803     qCDebug(KIO_CORE_DIRLISTER) << urlDir;
0804     const QList<QUrl> urls = directoriesForCanonicalPath(urlDir);
0805     for (const QUrl &u : urls) {
0806         updateDirectory(u);
0807     }
0808 }
0809 
0810 void KCoreDirListerCache::slotFilesRemoved(const QStringList &fileList) // from KDirNotify signals
0811 {
0812     slotFilesRemoved(QUrl::fromStringList(fileList));
0813 }
0814 
0815 void KCoreDirListerCache::slotFilesRemoved(const QList<QUrl> &fileList)
0816 {
0817     qCDebug(KIO_CORE_DIRLISTER) << fileList.count();
0818     // Group notifications by parent dirs (usually there would be only one parent dir)
0819     QMap<QUrl, KFileItemList> removedItemsByDir;
0820     QList<QUrl> deletedSubdirs;
0821 
0822     for (const QUrl &url : fileList) {
0823         const QList<QUrl> dirUrls = directoriesForCanonicalPath(url);
0824         for (const QUrl &dir : dirUrls) {
0825             DirItem *dirItem = dirItemForUrl(dir); // is it a listed directory?
0826             if (dirItem) {
0827                 deletedSubdirs.append(dir);
0828                 if (!dirItem->rootItem.isNull()) {
0829                     removedItemsByDir[url].append(dirItem->rootItem);
0830                 }
0831             }
0832         }
0833 
0834         const QUrl parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
0835         const QList<QUrl> parentDirUrls = directoriesForCanonicalPath(parentDir);
0836         for (const QUrl &dir : parentDirUrls) {
0837             DirItem *dirItem = dirItemForUrl(dir);
0838             if (!dirItem) {
0839                 continue;
0840             }
0841 
0842             auto dirItemIt = std::find_if(dirItem->lstItems.begin(), dirItem->lstItems.end(), [&url](const KFileItem &fitem) {
0843                 return fitem.name() == url.fileName();
0844             });
0845             if (dirItemIt != dirItem->lstItems.end()) {
0846                 const KFileItem fileitem = *dirItemIt;
0847                 removedItemsByDir[dir].append(fileitem);
0848                 // If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case.
0849                 if (fileitem.isNull() || fileitem.isDir()) {
0850                     deletedSubdirs.append(url);
0851                 }
0852                 dirItem->lstItems.erase(dirItemIt); // remove fileitem from list
0853             }
0854         }
0855     }
0856 
0857     for (auto rit = removedItemsByDir.constBegin(), cend = removedItemsByDir.constEnd(); rit != cend; ++rit) {
0858         // Tell the views about it before calling deleteDir.
0859         // They might need the subdirs' file items (see the dirtree).
0860         auto dit = directoryData.constFind(rit.key());
0861         if (dit != directoryData.constEnd()) {
0862             itemsDeleted((*dit).listersCurrentlyHolding, rit.value());
0863         }
0864     }
0865 
0866     for (const QUrl &url : std::as_const(deletedSubdirs)) {
0867         // in case of a dir, check if we have any known children, there's much to do in that case
0868         // (stopping jobs, removing dirs from cache etc.)
0869         deleteDir(url);
0870     }
0871 }
0872 
0873 void KCoreDirListerCache::slotFilesChanged(const QStringList &fileList) // from KDirNotify signals
0874 {
0875     qCDebug(KIO_CORE_DIRLISTER) << fileList;
0876     QList<QUrl> dirsToUpdate;
0877     for (const QString &fileUrl : fileList) {
0878         const QUrl url(fileUrl);
0879         const KFileItem &fileitem = findByUrl(nullptr, url);
0880         if (fileitem.isNull()) {
0881             qCDebug(KIO_CORE_DIRLISTER) << "item not found for" << url;
0882             continue;
0883         }
0884         if (url.isLocalFile()) {
0885             pendingUpdates.insert(url.toLocalFile()); // delegate the work to processPendingUpdates
0886         } else {
0887             pendingRemoteUpdates.insert(fileitem);
0888             // For remote files, we won't be able to figure out the new information,
0889             // we have to do a update (directory listing)
0890             const QUrl dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
0891             if (!dirsToUpdate.contains(dir)) {
0892                 dirsToUpdate.prepend(dir);
0893             }
0894         }
0895     }
0896 
0897     for (const QUrl &dirUrl : std::as_const(dirsToUpdate)) {
0898         updateDirectory(dirUrl);
0899     }
0900     // ## TODO problems with current jobs listing/updating that dir
0901     // ( see kde-2.2.2's kdirlister )
0902 
0903     processPendingUpdates();
0904 }
0905 
0906 void KCoreDirListerCache::slotFileRenamed(const QString &_src, const QString &_dst, const QString &dstPath) // from KDirNotify signals
0907 {
0908     QUrl src(_src);
0909     QUrl dst(_dst);
0910     qCDebug(KIO_CORE_DIRLISTER) << src << "->" << dst;
0911 #ifdef DEBUG_CACHE
0912     printDebug();
0913 #endif
0914 
0915     QUrl oldurl = src.adjusted(QUrl::StripTrailingSlash);
0916     KFileItem fileitem = findByUrl(nullptr, oldurl);
0917     if (fileitem.isNull()) {
0918         qCDebug(KIO_CORE_DIRLISTER) << "Item not found:" << oldurl;
0919         return;
0920     }
0921 
0922     const KFileItem oldItem = fileitem;
0923 
0924     // Dest already exists? Was overwritten then (testcase: #151851)
0925     // We better emit it as deleted -before- doing the renaming, otherwise
0926     // the "update" mechanism will emit the old one as deleted and
0927     // kdirmodel will delete the new (renamed) one!
0928     const KFileItem &existingDestItem = findByUrl(nullptr, dst);
0929     if (!existingDestItem.isNull()) {
0930         qCDebug(KIO_CORE_DIRLISTER) << dst << "already existed, let's delete it";
0931         slotFilesRemoved(QList<QUrl>{dst});
0932     }
0933 
0934     // If the item had a UDS_URL as well as UDS_NAME set, the user probably wants
0935     // to be updating the name only (since they can't see the URL).
0936     // Check to see if a URL exists, and if so, if only the file part has changed,
0937     // only update the name and not the underlying URL.
0938     bool nameOnly = !fileitem.entry().stringValue(KIO::UDSEntry::UDS_URL).isEmpty();
0939     nameOnly = nameOnly && src.adjusted(QUrl::RemoveFilename) == dst.adjusted(QUrl::RemoveFilename);
0940 
0941     if (!nameOnly && fileitem.isDir()) {
0942         renameDir(oldurl, dst);
0943         // #172945 - if the fileitem was the root item of a DirItem that was just removed from the cache,
0944         // then it's a dangling pointer now...
0945         fileitem = findByUrl(nullptr, oldurl);
0946         if (fileitem.isNull()) { // deleted from cache altogether, #188807
0947             return;
0948         }
0949     }
0950 
0951     // Now update the KFileItem representing that file or dir (not exclusive with the above!)
0952     if (!oldItem.isLocalFile() && !oldItem.localPath().isEmpty()
0953         && dstPath.isEmpty()) { // it uses UDS_LOCAL_PATH and we don't know the new path? needs an update then
0954         slotFilesChanged(QStringList{src.toString()});
0955     } else {
0956         const QUrl &itemOldUrl = fileitem.url();
0957         if (nameOnly) {
0958             fileitem.setName(dst.fileName());
0959         } else {
0960             fileitem.setUrl(dst);
0961         }
0962 
0963         if (!dstPath.isEmpty()) {
0964             fileitem.setLocalPath(dstPath);
0965         }
0966 
0967         fileitem.refreshMimeType();
0968         fileitem.determineMimeType();
0969         reinsert(fileitem, itemOldUrl);
0970 
0971         const std::set<KCoreDirLister *> listers = emitRefreshItem(oldItem, fileitem);
0972         for (KCoreDirLister *kdl : listers) {
0973             kdl->d->emitItems();
0974         }
0975     }
0976 
0977 #ifdef DEBUG_CACHE
0978     printDebug();
0979 #endif
0980 }
0981 
0982 std::set<KCoreDirLister *> KCoreDirListerCache::emitRefreshItem(const KFileItem &oldItem, const KFileItem &fileitem)
0983 {
0984     qCDebug(KIO_CORE_DIRLISTER) << "old:" << oldItem.name() << oldItem.url() << "new:" << fileitem.name() << fileitem.url();
0985     // Look whether this item was shown in any view, i.e. held by any dirlister
0986     const QUrl parentDir = oldItem.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
0987     DirectoryDataHash::iterator dit = directoryData.find(parentDir);
0988     QList<KCoreDirLister *> listers;
0989     // Also look in listersCurrentlyListing, in case the user manages to rename during a listing
0990     if (dit != directoryData.end()) {
0991         listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing;
0992     }
0993     if (oldItem.isDir()) {
0994         // For a directory, look for dirlisters where it's the root item.
0995         dit = directoryData.find(oldItem.url());
0996         if (dit != directoryData.end()) {
0997             listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing;
0998         }
0999     }
1000     std::set<KCoreDirLister *> listersToRefresh;
1001     for (KCoreDirLister *kdl : std::as_const(listers)) {
1002         // deduplicate listers
1003         listersToRefresh.insert(kdl);
1004     }
1005     for (KCoreDirLister *kdl : std::as_const(listersToRefresh)) {
1006         // For a directory, look for dirlisters where it's the root item.
1007         QUrl directoryUrl(oldItem.url());
1008         if (oldItem.isDir() && kdl->d->rootFileItem == oldItem) {
1009             const KFileItem oldRootItem = kdl->d->rootFileItem;
1010             kdl->d->rootFileItem = fileitem;
1011             kdl->d->addRefreshItem(directoryUrl, oldRootItem, fileitem);
1012         } else {
1013             directoryUrl = directoryUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
1014             kdl->d->addRefreshItem(directoryUrl, oldItem, fileitem);
1015         }
1016     }
1017     return listersToRefresh;
1018 }
1019 
1020 QList<QUrl> KCoreDirListerCache::directoriesForCanonicalPath(const QUrl &dir) const
1021 {
1022     QList<QUrl> urlList = canonicalUrls.value(dir);
1023     // make unique
1024     if (urlList.size() > 1) {
1025         std::sort(urlList.begin(), urlList.end());
1026         auto end_unique = std::unique(urlList.begin(), urlList.end());
1027         urlList.erase(end_unique, urlList.end());
1028     }
1029 
1030     QList<QUrl> dirs({dir});
1031     dirs.append(urlList);
1032 
1033     if (dirs.count() > 1) {
1034         qCDebug(KIO_CORE_DIRLISTER) << dir << "known as" << dirs;
1035     }
1036     return dirs;
1037 }
1038 
1039 // private slots
1040 
1041 // Called by KDirWatch - usually when a dir we're watching has been modified,
1042 // but it can also be called for a file.
1043 void KCoreDirListerCache::slotFileDirty(const QString &path)
1044 {
1045     qCDebug(KIO_CORE_DIRLISTER) << path;
1046     QUrl url = QUrl::fromLocalFile(path).adjusted(QUrl::StripTrailingSlash);
1047     // File or dir?
1048     bool isDir;
1049     const KFileItem item = itemForUrl(url);
1050 
1051     if (!item.isNull()) {
1052         isDir = item.isDir();
1053     } else {
1054         QFileInfo info(path);
1055         if (!info.exists()) {
1056             return; // error
1057         }
1058         isDir = info.isDir();
1059     }
1060 
1061     if (isDir) {
1062         const QList<QUrl> urls = directoriesForCanonicalPath(url);
1063         for (const QUrl &dir : urls) {
1064             handleDirDirty(dir);
1065         }
1066     }
1067     // Also do this for dirs, e.g. to handle permission changes
1068     const QList<QUrl> urls = directoriesForCanonicalPath(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
1069     for (const QUrl &dir : urls) {
1070         QUrl aliasUrl(dir);
1071         aliasUrl.setPath(Utils::concatPaths(aliasUrl.path(), url.fileName()));
1072         handleFileDirty(aliasUrl);
1073     }
1074 }
1075 
1076 // Called by slotFileDirty
1077 void KCoreDirListerCache::handleDirDirty(const QUrl &url)
1078 {
1079     // A dir: launch an update job if anyone cares about it
1080 
1081     // This also means we can forget about pending updates to individual files in that dir
1082     const QString dir = url.toLocalFile();
1083     const QString dirPath = Utils::slashAppended(dir);
1084 
1085     for (auto pendingIt = pendingUpdates.cbegin(); pendingIt != pendingUpdates.cend(); /* */) {
1086         const QString updPath = *pendingIt;
1087         qCDebug(KIO_CORE_DIRLISTER) << "had pending update" << updPath;
1088         if (updPath.startsWith(dirPath) && updPath.indexOf(QLatin1Char('/'), dirPath.length()) == -1) { // direct child item
1089             qCDebug(KIO_CORE_DIRLISTER) << "forgetting about individual update to" << updPath;
1090             pendingIt = pendingUpdates.erase(pendingIt);
1091         } else {
1092             ++pendingIt;
1093         }
1094     }
1095 
1096     if (checkUpdate(url)) {
1097         const auto [it, isInserted] = pendingDirectoryUpdates.insert(dir);
1098         if (isInserted && !pendingUpdateTimer.isActive()) {
1099             pendingUpdateTimer.start(200);
1100         }
1101     }
1102 }
1103 
1104 // Called by slotFileDirty, for every alias of <url>
1105 void KCoreDirListerCache::handleFileDirty(const QUrl &url)
1106 {
1107     // A file: do we know about it already?
1108     const KFileItem &existingItem = findByUrl(nullptr, url);
1109     const QUrl dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
1110     if (existingItem.isNull()) {
1111         // No - update the parent dir then
1112         handleDirDirty(dir);
1113     }
1114 
1115     // Delay updating the file, FAM is flooding us with events
1116     if (checkUpdate(dir)) {
1117         const QString filePath = url.toLocalFile();
1118         const auto [it, isInserted] = pendingUpdates.insert(filePath);
1119         if (isInserted && !pendingUpdateTimer.isActive()) {
1120             pendingUpdateTimer.start(200);
1121         }
1122     }
1123 }
1124 
1125 void KCoreDirListerCache::slotFileCreated(const QString &path) // from KDirWatch
1126 {
1127     qCDebug(KIO_CORE_DIRLISTER) << path;
1128     // XXX: how to avoid a complete rescan here?
1129     // We'd need to stat that one file separately and refresh the item(s) for it.
1130     QUrl fileUrl(QUrl::fromLocalFile(path));
1131     itemsAddedInDirectory(fileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
1132 }
1133 
1134 void KCoreDirListerCache::slotFileDeleted(const QString &path) // from KDirWatch
1135 {
1136     qCDebug(KIO_CORE_DIRLISTER) << path;
1137     const QString fileName = QFileInfo(path).fileName();
1138     QUrl dirUrl(QUrl::fromLocalFile(path));
1139     QStringList fileUrls;
1140     const QList<QUrl> urls = directoriesForCanonicalPath(dirUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
1141     for (const QUrl &url : urls) {
1142         QUrl urlInfo(url);
1143         urlInfo.setPath(Utils::concatPaths(urlInfo.path(), fileName));
1144         fileUrls << urlInfo.toString();
1145     }
1146     slotFilesRemoved(fileUrls);
1147 }
1148 
1149 void KCoreDirListerCache::slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries)
1150 {
1151     QUrl url(joburl(static_cast<KIO::ListJob *>(job)));
1152     url = url.adjusted(QUrl::StripTrailingSlash);
1153 
1154     qCDebug(KIO_CORE_DIRLISTER) << "new entries for " << url;
1155 
1156     DirItem *dir = itemsInUse.value(url);
1157     if (!dir) {
1158         qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but itemsInUse only knows about" << itemsInUse.keys();
1159         Q_ASSERT(dir);
1160         return;
1161     }
1162 
1163     DirectoryDataHash::iterator dit = directoryData.find(url);
1164     if (dit == directoryData.end()) {
1165         qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but directoryData doesn't know about that url, only about:" << directoryData.keys();
1166         Q_ASSERT(dit != directoryData.end());
1167         return;
1168     }
1169     KCoreDirListerCacheDirectoryData &dirData = *dit;
1170     const QList<KCoreDirLister *> listers = dirData.listersCurrentlyListing;
1171     if (listers.isEmpty()) {
1172         qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but directoryData says no listers are currently listing " << url;
1173 #ifndef NDEBUG
1174         printDebug();
1175 #endif
1176         Q_ASSERT(!listers.isEmpty());
1177         return;
1178     }
1179 
1180     // check if anyone wants the MIME types immediately
1181     bool delayedMimeTypes = true;
1182     for (const KCoreDirLister *kdl : listers) {
1183         delayedMimeTypes &= kdl->d->delayedMimeTypes;
1184     }
1185 
1186     CacheHiddenFile *cachedHidden = nullptr;
1187     bool dotHiddenChecked = false;
1188     KFileItemList newItems;
1189     for (const auto &entry : entries) {
1190         const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
1191 
1192         Q_ASSERT(!name.isEmpty());
1193         if (name.isEmpty()) {
1194             continue;
1195         }
1196 
1197         if (name == QLatin1Char('.')) {
1198             // Try to reuse an existing KFileItem (if we listed the parent dir)
1199             // rather than creating a new one. There are many reasons:
1200             // 1) renames and permission changes to the item would have to emit the signals
1201             // twice, otherwise, so that both views manage to recognize the item.
1202             // 2) with kio_ftp we can only know that something is a symlink when
1203             // listing the parent, so prefer that item, which has more info.
1204             // Note that it gives a funky name() to the root item, rather than "." ;)
1205             dir->rootItem = itemForUrl(url);
1206             if (dir->rootItem.isNull()) {
1207                 dir->rootItem = KFileItem(entry, url, delayedMimeTypes, true);
1208             }
1209 
1210             for (KCoreDirLister *kdl : listers) {
1211                 if (kdl->d->rootFileItem.isNull() && kdl->d->url == url) {
1212                     kdl->d->rootFileItem = dir->rootItem;
1213                 }
1214             }
1215         } else if (name != QLatin1String("..")) {
1216             KFileItem item(entry, url, delayedMimeTypes, true);
1217 
1218             // get the names of the files listed in ".hidden", if it exists and is a local file
1219             if (!dotHiddenChecked) {
1220                 const QString localPath = item.localPath();
1221                 if (!localPath.isEmpty()) {
1222                     const QString rootItemPath = QFileInfo(localPath).absolutePath();
1223                     cachedHidden = cachedDotHiddenForDir(rootItemPath);
1224                 }
1225                 dotHiddenChecked = true;
1226             }
1227 
1228             // hide file if listed in ".hidden"
1229             if (cachedHidden && cachedHidden->listedFiles.find(name) != cachedHidden->listedFiles.cend()) {
1230                 item.setHidden();
1231             }
1232 
1233             qCDebug(KIO_CORE_DIRLISTER) << "Adding item: " << item.url();
1234             newItems.append(item);
1235         }
1236     }
1237 
1238     // sort by url using KFileItem::operator<
1239     std::sort(newItems.begin(), newItems.end());
1240 
1241     // Add the items sorted by url, needed by findByUrl
1242     dir->insertSortedItems(newItems);
1243 
1244     for (KCoreDirLister *kdl : listers) {
1245         kdl->d->addNewItems(url, newItems);
1246     }
1247 
1248     for (KCoreDirLister *kdl : listers) {
1249         kdl->d->emitItems();
1250     }
1251 }
1252 
1253 void KCoreDirListerCache::slotResult(KJob *j)
1254 {
1255 #ifdef DEBUG_CACHE
1256     // printDebug();
1257 #endif
1258 
1259     Q_ASSERT(j);
1260     KIO::ListJob *job = static_cast<KIO::ListJob *>(j);
1261     runningListJobs.remove(job);
1262 
1263     QUrl jobUrl(joburl(job));
1264     jobUrl = jobUrl.adjusted(QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections
1265 
1266     qCDebug(KIO_CORE_DIRLISTER) << "finished listing" << jobUrl;
1267 
1268     const auto dit = directoryData.find(jobUrl);
1269     if (dit == directoryData.end()) {
1270         qCWarning(KIO_CORE) << "Nothing found in directoryData for URL" << jobUrl;
1271 #ifndef NDEBUG
1272         printDebug();
1273 #endif
1274         Q_ASSERT(dit != directoryData.end());
1275         return;
1276     }
1277     KCoreDirListerCacheDirectoryData &dirData = *dit;
1278     if (dirData.listersCurrentlyListing.isEmpty()) {
1279         qCWarning(KIO_CORE) << "OOOOPS, nothing in directoryData.listersCurrentlyListing for" << jobUrl;
1280         // We're about to assert; dump the current state...
1281 #ifndef NDEBUG
1282         printDebug();
1283 #endif
1284         Q_ASSERT(!dirData.listersCurrentlyListing.isEmpty());
1285     }
1286     const QList<KCoreDirLister *> listers = dirData.listersCurrentlyListing;
1287 
1288     // move all listers to the holding list, do it before emitting
1289     // the signals to make sure it exists in KCoreDirListerCache in case someone
1290     // calls listDir during the signal emission
1291     Q_ASSERT(dirData.listersCurrentlyHolding.isEmpty());
1292     dirData.moveListersWithoutCachedItemsJob(jobUrl);
1293 
1294     if (job->error()) {
1295         bool errorShown = false;
1296         for (KCoreDirLister *kdl : listers) {
1297             kdl->d->jobDone(job);
1298             if (job->error() != KJob::KilledJobError) {
1299                 Q_EMIT kdl->jobError(job);
1300                 if (kdl->d->m_autoErrorHandling && !errorShown) {
1301                     errorShown = true; // do it only once
1302                     if (job->uiDelegate()) {
1303                         job->uiDelegate()->showErrorMessage();
1304                     }
1305                 }
1306 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 82)
1307                 kdl->handleError(job);
1308 #endif
1309             }
1310             const bool silent = job->property("_kdlc_silent").toBool();
1311             if (!silent) {
1312 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1313                 Q_EMIT kdl->canceled(jobUrl);
1314 #endif
1315                 Q_EMIT kdl->listingDirCanceled(jobUrl);
1316             }
1317 
1318             if (kdl->d->numJobs() == 0) {
1319                 kdl->d->complete = true;
1320                 if (!silent) {
1321                     Q_EMIT kdl->canceled();
1322                 }
1323             }
1324         }
1325     } else {
1326         DirItem *dir = itemsInUse.value(jobUrl);
1327         Q_ASSERT(dir);
1328         dir->complete = true;
1329 
1330         for (KCoreDirLister *kdl : listers) {
1331             kdl->d->jobDone(job);
1332 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1333             Q_EMIT kdl->completed(jobUrl);
1334 #endif
1335             Q_EMIT kdl->listingDirCompleted(jobUrl);
1336             if (kdl->d->numJobs() == 0) {
1337                 kdl->d->complete = true;
1338                 Q_EMIT kdl->completed();
1339             }
1340         }
1341     }
1342 
1343     // TODO: hmm, if there was an error and job is a parent of one or more
1344     // of the pending urls we should cancel it/them as well
1345     processPendingUpdates();
1346 
1347     if (job->property("need_another_update").toBool()) {
1348         updateDirectory(jobUrl);
1349     }
1350 
1351 #ifdef DEBUG_CACHE
1352     printDebug();
1353 #endif
1354 }
1355 
1356 void KCoreDirListerCache::slotRedirection(KIO::Job *j, const QUrl &url)
1357 {
1358     Q_ASSERT(j);
1359     KIO::ListJob *job = static_cast<KIO::ListJob *>(j);
1360 
1361     QUrl oldUrl(job->url()); // here we really need the old url!
1362     QUrl newUrl(url);
1363 
1364     // strip trailing slashes
1365     oldUrl = oldUrl.adjusted(QUrl::StripTrailingSlash);
1366     newUrl = newUrl.adjusted(QUrl::StripTrailingSlash);
1367 
1368     if (oldUrl == newUrl) {
1369         qCDebug(KIO_CORE_DIRLISTER) << "New redirection url same as old, giving up.";
1370         return;
1371     } else if (newUrl.isEmpty()) {
1372         qCDebug(KIO_CORE_DIRLISTER) << "New redirection url is empty, giving up.";
1373         return;
1374     }
1375 
1376     qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl;
1377 
1378 #ifdef DEBUG_CACHE
1379     // Can't do that here. KCoreDirListerCache::joburl() will use the new url already,
1380     // while our data structures haven't been updated yet -> assert fail.
1381     // printDebug();
1382 #endif
1383 
1384     // I don't think there can be dirItems that are children of oldUrl.
1385     // Am I wrong here? And even if so, we don't need to delete them, right?
1386     // DF: redirection happens before listDir emits any item. Makes little sense otherwise.
1387 
1388     // oldUrl cannot be in itemsCached because only completed items are moved there
1389     DirItem *dir = itemsInUse.take(oldUrl);
1390     Q_ASSERT(dir);
1391 
1392     DirectoryDataHash::iterator dit = directoryData.find(oldUrl);
1393     Q_ASSERT(dit != directoryData.end());
1394     KCoreDirListerCacheDirectoryData oldDirData = *dit;
1395     directoryData.erase(dit);
1396     Q_ASSERT(!oldDirData.listersCurrentlyListing.isEmpty());
1397     const QList<KCoreDirLister *> listers = oldDirData.listersCurrentlyListing;
1398     Q_ASSERT(!listers.isEmpty());
1399 
1400     for (KCoreDirLister *kdl : listers) {
1401         kdl->d->redirect(oldUrl, newUrl, false /*clear items*/);
1402     }
1403 
1404     // when a lister was stopped before the job emits the redirection signal, the old url will
1405     // also be in listersCurrentlyHolding
1406     const QList<KCoreDirLister *> holders = oldDirData.listersCurrentlyHolding;
1407     for (KCoreDirLister *kdl : holders) {
1408         kdl->jobStarted(job);
1409         // do it like when starting a new list-job that will redirect later
1410         // TODO: maybe don't emit started if there's an update running for newUrl already?
1411         Q_EMIT kdl->started(oldUrl);
1412 
1413         kdl->d->redirect(oldUrl, newUrl, false /*clear items*/);
1414     }
1415 
1416     const QList<KCoreDirLister *> allListers = listers + holders;
1417 
1418     DirItem *newDir = itemsInUse.value(newUrl);
1419     if (newDir) {
1420         qCDebug(KIO_CORE_DIRLISTER) << newUrl << "already in use";
1421 
1422         // only in this case there can newUrl already be in listersCurrentlyListing or listersCurrentlyHolding
1423         delete dir;
1424 
1425         // get the job if one's running for newUrl already (can be a list-job or an update-job), but
1426         // do not return this 'job', which would happen because of the use of redirectionURL()
1427         KIO::ListJob *oldJob = jobForUrl(newUrl, job);
1428 
1429         // listers of newUrl with oldJob: forget about the oldJob and use the already running one
1430         // which will be converted to an updateJob
1431         KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl];
1432 
1433         QList<KCoreDirLister *> &curListers = newDirData.listersCurrentlyListing;
1434         if (!curListers.isEmpty()) {
1435             qCDebug(KIO_CORE_DIRLISTER) << "and it is currently listed";
1436 
1437             Q_ASSERT(oldJob); // ?!
1438 
1439             for (KCoreDirLister *kdl : std::as_const(curListers)) { // listers of newUrl
1440                 kdl->d->jobDone(oldJob);
1441 
1442                 kdl->jobStarted(job);
1443                 kdl->d->connectJob(job);
1444             }
1445 
1446             // append listers of oldUrl with newJob to listers of newUrl with oldJob
1447             for (KCoreDirLister *kdl : listers) {
1448                 curListers.append(kdl);
1449             }
1450         } else {
1451             curListers = listers;
1452         }
1453 
1454         if (oldJob) { // kill the old job, be it a list-job or an update-job
1455             killJob(oldJob);
1456         }
1457 
1458         // holders of newUrl: use the already running job which will be converted to an updateJob
1459         QList<KCoreDirLister *> &curHolders = newDirData.listersCurrentlyHolding;
1460         if (!curHolders.isEmpty()) {
1461             qCDebug(KIO_CORE_DIRLISTER) << "and it is currently held.";
1462 
1463             for (KCoreDirLister *kdl : std::as_const(curHolders)) { // holders of newUrl
1464                 kdl->jobStarted(job);
1465                 Q_EMIT kdl->started(newUrl);
1466             }
1467 
1468             // append holders of oldUrl to holders of newUrl
1469             for (KCoreDirLister *kdl : holders) {
1470                 curHolders.append(kdl);
1471             }
1472         } else {
1473             curHolders = holders;
1474         }
1475 
1476         // emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed
1477         // TODO: make this a separate method?
1478         for (KCoreDirLister *kdl : allListers) {
1479             if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) {
1480                 kdl->d->rootFileItem = newDir->rootItem;
1481             }
1482 
1483             kdl->d->addNewItems(newUrl, newDir->lstItems);
1484             kdl->d->emitItems();
1485         }
1486     } else if ((newDir = itemsCached.take(newUrl))) {
1487         qCDebug(KIO_CORE_DIRLISTER) << newUrl << "is unused, but already in the cache.";
1488 
1489         delete dir;
1490         itemsInUse.insert(newUrl, newDir);
1491         KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl];
1492         newDirData.listersCurrentlyListing = listers;
1493         newDirData.listersCurrentlyHolding = holders;
1494 
1495         // emit old items: listers, holders
1496         for (KCoreDirLister *kdl : allListers) {
1497             if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) {
1498                 kdl->d->rootFileItem = newDir->rootItem;
1499             }
1500 
1501             kdl->d->addNewItems(newUrl, newDir->lstItems);
1502             kdl->d->emitItems();
1503         }
1504     } else {
1505         qCDebug(KIO_CORE_DIRLISTER) << newUrl << "has not been listed yet.";
1506 
1507         dir->rootItem = KFileItem();
1508         dir->lstItems.clear();
1509         dir->redirect(newUrl);
1510         itemsInUse.insert(newUrl, dir);
1511         KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl];
1512         newDirData.listersCurrentlyListing = listers;
1513         newDirData.listersCurrentlyHolding = holders;
1514 
1515         if (holders.isEmpty()) {
1516 #ifdef DEBUG_CACHE
1517             printDebug();
1518 #endif
1519             return; // only in this case the job doesn't need to be converted,
1520         }
1521     }
1522 
1523     // make the job an update job
1524     job->disconnect(this);
1525 
1526     connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotUpdateEntries);
1527     connect(job, &KJob::result, this, &KCoreDirListerCache::slotUpdateResult);
1528 
1529     // FIXME: autoUpdate-Counts!!
1530 
1531 #ifdef DEBUG_CACHE
1532     printDebug();
1533 #endif
1534 }
1535 
1536 struct KCoreDirListerCache::ItemInUseChange {
1537     ItemInUseChange(const QUrl &old, const QUrl &newU, DirItem *di)
1538         : oldUrl(old)
1539         , newUrl(newU)
1540         , dirItem(di)
1541     {
1542     }
1543     QUrl oldUrl;
1544     QUrl newUrl;
1545     DirItem *dirItem;
1546 };
1547 
1548 void KCoreDirListerCache::renameDir(const QUrl &oldUrl, const QUrl &newUrl)
1549 {
1550     qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl;
1551 
1552     std::vector<ItemInUseChange> itemsToChange;
1553     std::set<KCoreDirLister *> listers;
1554 
1555     // Look at all dirs being listed/shown
1556     for (auto itu = itemsInUse.begin(), ituend = itemsInUse.end(); itu != ituend; ++itu) {
1557         DirItem *dir = itu.value();
1558         const QUrl &oldDirUrl = itu.key();
1559         qCDebug(KIO_CORE_DIRLISTER) << "itemInUse:" << oldDirUrl;
1560         // Check if this dir is oldUrl, or a subfolder of it
1561         if (oldDirUrl == oldUrl || oldUrl.isParentOf(oldDirUrl)) {
1562             // TODO should use KUrl::cleanpath like isParentOf does
1563             QString relPath = oldDirUrl.path().mid(oldUrl.path().length() + 1);
1564 
1565             QUrl newDirUrl(newUrl); // take new base
1566             if (!relPath.isEmpty()) {
1567                 newDirUrl.setPath(Utils::concatPaths(newDirUrl.path(), relPath)); // add unchanged relative path
1568             }
1569             qCDebug(KIO_CORE_DIRLISTER) << "new url=" << newDirUrl;
1570 
1571             // Update URL in dir item and in itemsInUse
1572             dir->redirect(newDirUrl);
1573 
1574             itemsToChange.emplace_back(oldDirUrl.adjusted(QUrl::StripTrailingSlash), newDirUrl.adjusted(QUrl::StripTrailingSlash), dir);
1575             // Rename all items under that dir
1576             // If all items of the directory change the same part of their url, the order is not
1577             // changed, therefore just change it in the list.
1578             for (KFileItem &item : dir->lstItems) {
1579                 const KFileItem oldItem = item;
1580                 KFileItem newItem = oldItem;
1581                 const QUrl &oldItemUrl = oldItem.url();
1582                 QUrl newItemUrl(oldItemUrl);
1583                 newItemUrl.setPath(Utils::concatPaths(newDirUrl.path(), oldItemUrl.fileName()));
1584                 qCDebug(KIO_CORE_DIRLISTER) << "renaming" << oldItemUrl << "to" << newItemUrl;
1585                 newItem.setUrl(newItemUrl);
1586 
1587                 listers.merge(emitRefreshItem(oldItem, newItem));
1588                 // Change the item
1589                 item.setUrl(newItemUrl);
1590             }
1591         }
1592     }
1593 
1594     for (KCoreDirLister *kdl : listers) {
1595         kdl->d->emitItems();
1596     }
1597 
1598     // Do the changes to itemsInUse out of the loop to avoid messing up iterators,
1599     // and so that emitRefreshItem can find the stuff in the hash.
1600     for (const ItemInUseChange &i : itemsToChange) {
1601         itemsInUse.remove(i.oldUrl);
1602         itemsInUse.insert(i.newUrl, i.dirItem);
1603     }
1604     // Now that all the caches are updated and consistent, emit the redirection.
1605     for (const ItemInUseChange &i : itemsToChange) {
1606         emitRedirections(QUrl(i.oldUrl), QUrl(i.newUrl));
1607     }
1608     // Is oldUrl a directory in the cache?
1609     // Remove any child of oldUrl from the cache - even if the renamed dir itself isn't in it!
1610     removeDirFromCache(oldUrl);
1611     // TODO rename, instead.
1612 }
1613 
1614 // helper for renameDir, not used for redirections from KIO::listDir().
1615 void KCoreDirListerCache::emitRedirections(const QUrl &_oldUrl, const QUrl &_newUrl)
1616 {
1617     qCDebug(KIO_CORE_DIRLISTER) << _oldUrl << "->" << _newUrl;
1618     const QUrl oldUrl = _oldUrl.adjusted(QUrl::StripTrailingSlash);
1619     const QUrl newUrl = _newUrl.adjusted(QUrl::StripTrailingSlash);
1620 
1621     KIO::ListJob *job = jobForUrl(oldUrl);
1622     if (job) {
1623         killJob(job);
1624     }
1625 
1626     // Check if we were listing this dir. Need to abort and restart with new name in that case.
1627     DirectoryDataHash::iterator dit = directoryData.find(oldUrl);
1628     if (dit == directoryData.end()) {
1629         return;
1630     }
1631     const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing;
1632     const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding;
1633 
1634     KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl];
1635 
1636     // Tell the world that the job listing the old url is dead.
1637     for (KCoreDirLister *kdl : listers) {
1638         if (job) {
1639             kdl->d->jobDone(job);
1640         }
1641 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1642         Q_EMIT kdl->canceled(oldUrl);
1643 #endif
1644         Q_EMIT kdl->listingDirCanceled(oldUrl);
1645     }
1646     newDirData.listersCurrentlyListing += listers;
1647 
1648     // Check if we are currently displaying this directory (odds opposite wrt above)
1649     for (KCoreDirLister *kdl : holders) {
1650         if (job) {
1651             kdl->d->jobDone(job);
1652         }
1653     }
1654     newDirData.listersCurrentlyHolding += holders;
1655     directoryData.erase(dit);
1656 
1657     if (!listers.isEmpty()) {
1658         updateDirectory(newUrl);
1659 
1660         // Tell the world about the new url
1661         for (KCoreDirLister *kdl : listers) {
1662             Q_EMIT kdl->started(newUrl);
1663         }
1664     }
1665 
1666     // And notify the dirlisters of the redirection
1667     for (KCoreDirLister *kdl : holders) {
1668         kdl->d->redirect(oldUrl, newUrl, true /*keep items*/);
1669     }
1670 }
1671 
1672 void KCoreDirListerCache::removeDirFromCache(const QUrl &dir)
1673 {
1674     qCDebug(KIO_CORE_DIRLISTER) << dir;
1675     const QList<QUrl> cachedDirs = itemsCached.keys(); // seems slow, but there's no qcache iterator...
1676     for (const QUrl &cachedDir : cachedDirs) {
1677         if (dir == cachedDir || dir.isParentOf(cachedDir)) {
1678             itemsCached.remove(cachedDir);
1679         }
1680     }
1681 }
1682 
1683 void KCoreDirListerCache::slotUpdateEntries(KIO::Job *job, const KIO::UDSEntryList &list)
1684 {
1685     runningListJobs[static_cast<KIO::ListJob *>(job)] += list;
1686 }
1687 
1688 void KCoreDirListerCache::slotUpdateResult(KJob *j)
1689 {
1690     Q_ASSERT(j);
1691     KIO::ListJob *job = static_cast<KIO::ListJob *>(j);
1692 
1693     QUrl jobUrl(joburl(job));
1694     jobUrl = jobUrl.adjusted(QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections
1695 
1696     qCDebug(KIO_CORE_DIRLISTER) << "finished update" << jobUrl;
1697 
1698     KCoreDirListerCacheDirectoryData &dirData = directoryData[jobUrl];
1699     // Collect the dirlisters which were listing the URL using that ListJob
1700     // plus those that were already holding that URL - they all get updated.
1701     dirData.moveListersWithoutCachedItemsJob(jobUrl);
1702     const QList<KCoreDirLister *> listers = dirData.listersCurrentlyHolding + dirData.listersCurrentlyListing;
1703 
1704     // once we are updating dirs that are only in the cache this will fail!
1705     Q_ASSERT(!listers.isEmpty());
1706 
1707     if (job->error()) {
1708         for (KCoreDirLister *kdl : listers) {
1709             kdl->d->jobDone(job);
1710 
1711             // don't bother the user: no jobError signal emitted
1712 
1713             const bool silent = job->property("_kdlc_silent").toBool();
1714             if (!silent) {
1715 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1716                 Q_EMIT kdl->canceled(jobUrl);
1717 #endif
1718                 Q_EMIT kdl->listingDirCanceled(jobUrl);
1719             }
1720             if (kdl->d->numJobs() == 0) {
1721                 kdl->d->complete = true;
1722                 if (!silent) {
1723                     Q_EMIT kdl->canceled();
1724                 }
1725             }
1726         }
1727 
1728         runningListJobs.remove(job);
1729 
1730         // TODO: if job is a parent of one or more
1731         // of the pending urls we should cancel them
1732         processPendingUpdates();
1733         return;
1734     }
1735 
1736     DirItem *dir = itemsInUse.value(jobUrl, nullptr);
1737     if (!dir) {
1738         qCWarning(KIO_CORE) << "Internal error: itemsInUse did not contain" << jobUrl;
1739 #ifndef NDEBUG
1740         printDebug();
1741 #endif
1742         Q_ASSERT(dir);
1743     } else {
1744         dir->complete = true;
1745     }
1746 
1747     // check if anyone wants the MIME types immediately
1748     bool delayedMimeTypes = true;
1749     for (const KCoreDirLister *kdl : listers) {
1750         delayedMimeTypes &= kdl->d->delayedMimeTypes;
1751     }
1752 
1753     typedef QHash<QString, KFileItem> FileItemHash; // fileName -> KFileItem
1754     FileItemHash fileItems;
1755 
1756     // Fill the hash from the old list of items. We'll remove entries as we see them
1757     // in the new listing, and the resulting hash entries will be the deleted items.
1758     for (const KFileItem &item : std::as_const(dir->lstItems)) {
1759         fileItems.insert(item.name(), item);
1760     }
1761 
1762     CacheHiddenFile *cachedHidden = nullptr;
1763     bool dotHiddenChecked = false;
1764     const KIO::UDSEntryList &buf = runningListJobs.value(job);
1765     KFileItemList newItems;
1766     for (const auto &entry : buf) {
1767         // Form the complete url
1768         KFileItem item(entry, jobUrl, delayedMimeTypes, true);
1769 
1770         const QString name = item.name();
1771         Q_ASSERT(!name.isEmpty()); // A KIO worker setting an empty UDS_NAME is utterly broken, fix the KIO worker!
1772 
1773         // we duplicate the check for dotdot here, to avoid iterating over
1774         // all items again and checking in matchesFilter() that way.
1775         if (name.isEmpty() || name == QLatin1String("..")) {
1776             continue;
1777         }
1778 
1779         if (name == QLatin1Char('.')) {
1780             // if the update was started before finishing the original listing
1781             // there is no root item yet
1782             if (dir->rootItem.isNull()) {
1783                 dir->rootItem = item;
1784 
1785                 for (KCoreDirLister *kdl : listers) {
1786                     if (kdl->d->rootFileItem.isNull() && kdl->d->url == jobUrl) {
1787                         kdl->d->rootFileItem = dir->rootItem;
1788                     }
1789                 }
1790             }
1791             continue;
1792         } else {
1793             // get the names of the files listed in ".hidden", if it exists and is a local file
1794             if (!dotHiddenChecked) {
1795                 const QString localPath = item.localPath();
1796                 if (!localPath.isEmpty()) {
1797                     const QString rootItemPath = QFileInfo(localPath).absolutePath();
1798                     cachedHidden = cachedDotHiddenForDir(rootItemPath);
1799                 }
1800                 dotHiddenChecked = true;
1801             }
1802         }
1803 
1804         // hide file if listed in ".hidden"
1805         if (cachedHidden && cachedHidden->listedFiles.find(name) != cachedHidden->listedFiles.cend()) {
1806             item.setHidden();
1807         }
1808 
1809         // Find this item
1810         FileItemHash::iterator fiit = fileItems.find(item.name());
1811         if (fiit != fileItems.end()) {
1812             const KFileItem tmp = fiit.value();
1813             auto pru_it = pendingRemoteUpdates.find(tmp);
1814             const bool inPendingRemoteUpdates = pru_it != pendingRemoteUpdates.end();
1815 
1816             // check if something changed for this file, using KFileItem::cmp()
1817             if (!tmp.cmp(item) || inPendingRemoteUpdates) {
1818                 if (inPendingRemoteUpdates) {
1819                     pendingRemoteUpdates.erase(pru_it);
1820                 }
1821 
1822                 qCDebug(KIO_CORE_DIRLISTER) << "file changed:" << tmp.name();
1823 
1824                 reinsert(item, tmp.url());
1825                 for (KCoreDirLister *kdl : listers) {
1826                     kdl->d->addRefreshItem(jobUrl, tmp, item);
1827                 }
1828             }
1829             // Seen, remove
1830             fileItems.erase(fiit);
1831         } else { // this is a new file
1832             qCDebug(KIO_CORE_DIRLISTER) << "new file:" << name;
1833             newItems.append(item);
1834         }
1835     }
1836 
1837     // sort by url using KFileItem::operator<
1838     std::sort(newItems.begin(), newItems.end());
1839 
1840     // Add the items sorted by url, needed by findByUrl
1841     dir->insertSortedItems(newItems);
1842 
1843     for (KCoreDirLister *kdl : listers) {
1844         kdl->d->addNewItems(jobUrl, newItems);
1845     }
1846 
1847     runningListJobs.remove(job);
1848 
1849     if (!fileItems.isEmpty()) {
1850         deleteUnmarkedItems(listers, dir->lstItems, fileItems);
1851     }
1852 
1853     for (KCoreDirLister *kdl : listers) {
1854         kdl->d->emitItems();
1855 
1856         kdl->d->jobDone(job);
1857 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1858         Q_EMIT kdl->completed(jobUrl);
1859 #endif
1860         Q_EMIT kdl->listingDirCompleted(jobUrl);
1861         if (kdl->d->numJobs() == 0) {
1862             kdl->d->complete = true;
1863             Q_EMIT kdl->completed();
1864         }
1865     }
1866 
1867     // TODO: hmm, if there was an error and job is a parent of one or more
1868     // of the pending urls we should cancel it/them as well
1869     processPendingUpdates();
1870 
1871     if (job->property("need_another_update").toBool()) {
1872         updateDirectory(jobUrl);
1873     }
1874 }
1875 
1876 // private
1877 
1878 KIO::ListJob *KCoreDirListerCache::jobForUrl(const QUrl &url, KIO::ListJob *not_job)
1879 {
1880     for (auto it = runningListJobs.cbegin(); it != runningListJobs.cend(); ++it) {
1881         KIO::ListJob *job = it.key();
1882         const QUrl jobUrl = joburl(job).adjusted(QUrl::StripTrailingSlash);
1883 
1884         if (jobUrl == url && job != not_job) {
1885             return job;
1886         }
1887     }
1888     return nullptr;
1889 }
1890 
1891 const QUrl &KCoreDirListerCache::joburl(KIO::ListJob *job)
1892 {
1893     if (job->redirectionUrl().isValid()) {
1894         return job->redirectionUrl();
1895     } else {
1896         return job->url();
1897     }
1898 }
1899 
1900 void KCoreDirListerCache::killJob(KIO::ListJob *job)
1901 {
1902     runningListJobs.remove(job);
1903     job->disconnect(this);
1904     job->kill();
1905 }
1906 
1907 void KCoreDirListerCache::deleteUnmarkedItems(const QList<KCoreDirLister *> &listers,
1908                                               QList<KFileItem> &lstItems,
1909                                               const QHash<QString, KFileItem> &itemsToDelete)
1910 {
1911     // Make list of deleted items (for emitting)
1912     KFileItemList deletedItems;
1913     deletedItems.reserve(itemsToDelete.size());
1914     for (auto kit = itemsToDelete.cbegin(), endIt = itemsToDelete.cend(); kit != endIt; ++kit) {
1915         const KFileItem item = kit.value();
1916         deletedItems.append(item);
1917         qCDebug(KIO_CORE_DIRLISTER) << "deleted:" << item.name() << item;
1918     }
1919 
1920     // Delete all remaining items
1921     auto it = std::remove_if(lstItems.begin(), lstItems.end(), [&itemsToDelete](const KFileItem &item) {
1922         return itemsToDelete.contains(item.name());
1923     });
1924     lstItems.erase(it, lstItems.end());
1925 
1926     itemsDeleted(listers, deletedItems);
1927 }
1928 
1929 void KCoreDirListerCache::itemsDeleted(const QList<KCoreDirLister *> &listers, const KFileItemList &deletedItems)
1930 {
1931     for (KCoreDirLister *kdl : listers) {
1932         kdl->d->emitItemsDeleted(deletedItems);
1933     }
1934 
1935     for (const KFileItem &item : deletedItems) {
1936         if (item.isDir()) {
1937             deleteDir(item.url());
1938         }
1939     }
1940 }
1941 
1942 void KCoreDirListerCache::deleteDir(const QUrl &_dirUrl)
1943 {
1944     qCDebug(KIO_CORE_DIRLISTER) << _dirUrl;
1945     // unregister and remove the children of the deleted item.
1946     // Idea: tell all the KCoreDirListers that they should forget the dir
1947     //       and then remove it from the cache.
1948 
1949     QUrl dirUrl(_dirUrl.adjusted(QUrl::StripTrailingSlash));
1950 
1951     // Separate itemsInUse iteration and calls to forgetDirs (which modify itemsInUse)
1952     QList<QUrl> affectedItems;
1953 
1954     auto itu = itemsInUse.cbegin();
1955     const auto ituend = itemsInUse.cend();
1956     for (; itu != ituend; ++itu) {
1957         const QUrl &deletedUrl = itu.key();
1958         if (dirUrl == deletedUrl || dirUrl.isParentOf(deletedUrl)) {
1959             affectedItems.append(deletedUrl);
1960         }
1961     }
1962 
1963     for (const QUrl &deletedUrl : std::as_const(affectedItems)) {
1964         // stop all jobs for deletedUrlStr
1965         auto dit = directoryData.constFind(deletedUrl);
1966         if (dit != directoryData.cend()) {
1967             // we need a copy because stop modifies the list
1968             const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing;
1969             for (KCoreDirLister *kdl : listers) {
1970                 stopListingUrl(kdl, deletedUrl);
1971             }
1972             // tell listers holding deletedUrl to forget about it
1973             // this will stop running updates for deletedUrl as well
1974 
1975             // we need a copy because forgetDirs modifies the list
1976             const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding;
1977             for (KCoreDirLister *kdl : holders) {
1978                 // lister's root is the deleted item
1979                 if (kdl->d->url == deletedUrl) {
1980                     // tell the view first. It might need the subdirs' items (which forgetDirs will delete)
1981                     if (!kdl->d->rootFileItem.isNull()) {
1982                         Q_EMIT kdl->itemsDeleted(KFileItemList{kdl->d->rootFileItem});
1983                     }
1984                     forgetDirs(kdl);
1985                     kdl->d->rootFileItem = KFileItem();
1986                 } else {
1987                     const bool treeview = kdl->d->lstDirs.count() > 1;
1988                     if (!treeview) {
1989                         Q_EMIT kdl->clear();
1990                         kdl->d->lstDirs.clear();
1991                     } else {
1992                         kdl->d->lstDirs.removeAll(deletedUrl);
1993                     }
1994 
1995                     forgetDirs(kdl, deletedUrl, treeview);
1996                 }
1997             }
1998         }
1999 
2000         // delete the entry for deletedUrl - should not be needed, it's in
2001         // items cached now
2002         int count = itemsInUse.remove(deletedUrl);
2003         Q_ASSERT(count == 0);
2004         Q_UNUSED(count); // keep gcc "unused variable" complaining quiet when in release mode
2005     }
2006 
2007     // remove the children from the cache
2008     removeDirFromCache(dirUrl);
2009 }
2010 
2011 // delayed updating of files, FAM is flooding us with events
2012 void KCoreDirListerCache::processPendingUpdates()
2013 {
2014     std::set<KCoreDirLister *> listers;
2015     for (const QString &file : pendingUpdates) { // always a local path
2016         qCDebug(KIO_CORE_DIRLISTER) << file;
2017         QUrl u = QUrl::fromLocalFile(file);
2018         KFileItem item = findByUrl(nullptr, u); // search all items
2019         if (!item.isNull()) {
2020             // we need to refresh the item, because e.g. the permissions can have changed.
2021             KFileItem oldItem = item;
2022             item.refresh();
2023 
2024             if (!oldItem.cmp(item)) {
2025                 reinsert(item, oldItem.url());
2026                 listers.merge(emitRefreshItem(oldItem, item));
2027             }
2028         }
2029     }
2030     pendingUpdates.clear();
2031     for (KCoreDirLister *kdl : listers) {
2032         kdl->d->emitItems();
2033     }
2034 
2035     // Directories in need of updating
2036     for (const QString &dir : pendingDirectoryUpdates) {
2037         updateDirectory(QUrl::fromLocalFile(dir));
2038     }
2039     pendingDirectoryUpdates.clear();
2040 }
2041 
2042 #ifndef NDEBUG
2043 void KCoreDirListerCache::printDebug()
2044 {
2045     qCDebug(KIO_CORE_DIRLISTER) << "Items in use:";
2046     auto itu = itemsInUse.constBegin();
2047     const auto ituend = itemsInUse.constEnd();
2048     for (; itu != ituend; ++itu) {
2049         qCDebug(KIO_CORE_DIRLISTER) << "   " << itu.key() << "URL:" << itu.value()->url
2050                                     << "rootItem:" << (!itu.value()->rootItem.isNull() ? itu.value()->rootItem.url() : QUrl())
2051                                     << "autoUpdates refcount:" << itu.value()->autoUpdates << "complete:" << itu.value()->complete
2052                                     << QStringLiteral("with %1 items.").arg(itu.value()->lstItems.count());
2053     }
2054 
2055     QList<KCoreDirLister *> listersWithoutJob;
2056     qCDebug(KIO_CORE_DIRLISTER) << "Directory data:";
2057     auto dit = directoryData.constBegin();
2058     for (; dit != directoryData.constEnd(); ++dit) {
2059         QString list;
2060         const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing;
2061         for (KCoreDirLister *listit : listers) {
2062             list += QLatin1String(" 0x") + QString::number(reinterpret_cast<qlonglong>(listit), 16);
2063         }
2064         qCDebug(KIO_CORE_DIRLISTER) << "  " << dit.key() << listers.count() << "listers:" << list;
2065         for (KCoreDirLister *listit : listers) {
2066             if (!listit->d->m_cachedItemsJobs.isEmpty()) {
2067                 qCDebug(KIO_CORE_DIRLISTER) << "  Lister" << listit << "has CachedItemsJobs" << listit->d->m_cachedItemsJobs;
2068             } else if (KIO::ListJob *listJob = jobForUrl(dit.key())) {
2069                 qCDebug(KIO_CORE_DIRLISTER) << "  Lister" << listit << "has ListJob" << listJob;
2070             } else {
2071                 listersWithoutJob.append(listit);
2072             }
2073         }
2074 
2075         list.clear();
2076         const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding;
2077         for (KCoreDirLister *listit : holders) {
2078             list += QLatin1String(" 0x") + QString::number(reinterpret_cast<qlonglong>(listit), 16);
2079         }
2080         qCDebug(KIO_CORE_DIRLISTER) << "  " << dit.key() << holders.count() << "holders:" << list;
2081     }
2082 
2083     QMap<KIO::ListJob *, KIO::UDSEntryList>::Iterator jit = runningListJobs.begin();
2084     qCDebug(KIO_CORE_DIRLISTER) << "Jobs:";
2085     for (; jit != runningListJobs.end(); ++jit) {
2086         qCDebug(KIO_CORE_DIRLISTER) << "   " << jit.key() << "listing" << joburl(jit.key()) << ":" << (*jit).count() << "entries.";
2087     }
2088 
2089     qCDebug(KIO_CORE_DIRLISTER) << "Items in cache:";
2090     const QList<QUrl> cachedDirs = itemsCached.keys();
2091     for (const QUrl &cachedDir : cachedDirs) {
2092         DirItem *dirItem = itemsCached.object(cachedDir);
2093         qCDebug(KIO_CORE_DIRLISTER) << "   " << cachedDir
2094                                     << "rootItem:" << (!dirItem->rootItem.isNull() ? dirItem->rootItem.url().toString() : QStringLiteral("NULL")) << "with"
2095                                     << dirItem->lstItems.count() << "items.";
2096     }
2097 
2098     // Abort on listers without jobs -after- showing the full dump. Easier debugging.
2099     for (KCoreDirLister *listit : std::as_const(listersWithoutJob)) {
2100         qCWarning(KIO_CORE) << "Fatal Error: HUH? Lister" << listit << "is supposed to be listing, but has no job!";
2101         abort();
2102     }
2103 }
2104 #endif
2105 
2106 KCoreDirLister::KCoreDirLister(QObject *parent)
2107     : QObject(parent)
2108     , d(new KCoreDirListerPrivate(this))
2109 {
2110     qCDebug(KIO_CORE_DIRLISTER) << "+KCoreDirLister";
2111 
2112     d->complete = true;
2113 
2114     setAutoUpdate(true);
2115     setDirOnlyMode(false);
2116     setShowHiddenFiles(false);
2117 }
2118 
2119 KCoreDirLister::~KCoreDirLister()
2120 {
2121     qCDebug(KIO_CORE_DIRLISTER) << "~KCoreDirLister" << this;
2122 
2123     // Stop all running jobs, remove lister from lists
2124     if (!kDirListerCache.isDestroyed()) {
2125         stop();
2126         kDirListerCache()->forgetDirs(this);
2127     }
2128 }
2129 
2130 // TODO KF6: remove bool ret val, it's always true
2131 bool KCoreDirLister::openUrl(const QUrl &_url, OpenUrlFlags _flags)
2132 {
2133     // emit the current changes made to avoid an inconsistent treeview
2134     if (d->hasPendingChanges && (_flags & Keep)) {
2135         emitChanges();
2136     }
2137 
2138     d->hasPendingChanges = false;
2139 
2140     return kDirListerCache()->listDir(this, _url, _flags & Keep, _flags & Reload);
2141 }
2142 
2143 void KCoreDirLister::stop()
2144 {
2145     kDirListerCache()->stop(this);
2146 }
2147 
2148 void KCoreDirLister::stop(const QUrl &_url)
2149 {
2150     kDirListerCache()->stopListingUrl(this, _url);
2151 }
2152 
2153 void KCoreDirLister::forgetDirs(const QUrl &_url)
2154 {
2155     kDirListerCache()->forgetDirs(this, _url, true);
2156 }
2157 
2158 bool KCoreDirLister::autoUpdate() const
2159 {
2160     return d->autoUpdate;
2161 }
2162 
2163 void KCoreDirLister::setAutoUpdate(bool enable)
2164 {
2165     if (d->autoUpdate == enable) {
2166         return;
2167     }
2168 
2169     d->autoUpdate = enable;
2170     kDirListerCache()->setAutoUpdate(this, enable);
2171 }
2172 
2173 bool KCoreDirLister::showHiddenFiles() const
2174 {
2175     return d->settings.isShowingDotFiles;
2176 }
2177 
2178 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100)
2179 bool KCoreDirLister::showingDotFiles() const
2180 {
2181     return showHiddenFiles();
2182 }
2183 #endif
2184 
2185 void KCoreDirLister::setShowHiddenFiles(bool setShowHiddenFiles)
2186 {
2187     if (d->settings.isShowingDotFiles == setShowHiddenFiles) {
2188         return;
2189     }
2190 
2191     d->prepareForSettingsChange();
2192     d->settings.isShowingDotFiles = setShowHiddenFiles;
2193 }
2194 
2195 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100)
2196 void KCoreDirLister::setShowingDotFiles(bool showDotFiles)
2197 {
2198     setShowHiddenFiles(showDotFiles);
2199 }
2200 #endif
2201 
2202 bool KCoreDirLister::dirOnlyMode() const
2203 {
2204     return d->settings.dirOnlyMode;
2205 }
2206 
2207 void KCoreDirLister::setDirOnlyMode(bool dirsOnly)
2208 {
2209     if (d->settings.dirOnlyMode == dirsOnly) {
2210         return;
2211     }
2212 
2213     d->prepareForSettingsChange();
2214     d->settings.dirOnlyMode = dirsOnly;
2215 }
2216 
2217 bool KCoreDirLister::requestMimeTypeWhileListing() const
2218 {
2219     return d->requestMimeTypeWhileListing;
2220 }
2221 
2222 void KCoreDirLister::setRequestMimeTypeWhileListing(bool request)
2223 {
2224     if (d->requestMimeTypeWhileListing == request) {
2225         return;
2226     }
2227 
2228     d->requestMimeTypeWhileListing = request;
2229     if (d->requestMimeTypeWhileListing) {
2230         // Changing from request off to on, clear any cached items associated
2231         // with this lister so we re-request them and get the mimetype as well.
2232         // If we do not, we risk caching items that have no mime type.
2233         kDirListerCache()->forgetDirs(this);
2234     }
2235 }
2236 
2237 QUrl KCoreDirLister::url() const
2238 {
2239     return d->url;
2240 }
2241 
2242 QList<QUrl> KCoreDirLister::directories() const
2243 {
2244     return d->lstDirs;
2245 }
2246 
2247 void KCoreDirLister::emitChanges()
2248 {
2249     d->emitChanges();
2250 }
2251 
2252 void KCoreDirListerPrivate::emitChanges()
2253 {
2254     if (!hasPendingChanges) {
2255         return;
2256     }
2257 
2258     // reset 'hasPendingChanges' now, in case of recursion
2259     // (testcase: enabling recursive scan in ktorrent, #174920)
2260     hasPendingChanges = false;
2261 
2262     const KCoreDirListerPrivate::FilterSettings newSettings = settings;
2263     settings = oldSettings; // temporarily
2264 
2265     // Fill hash with all items that are currently visible
2266     std::set<QString> oldVisibleItems;
2267     for (const QUrl &dir : std::as_const(lstDirs)) {
2268         const QList<KFileItem> *itemList = kDirListerCache()->itemsForDir(dir);
2269         if (!itemList) {
2270             continue;
2271         }
2272 
2273         for (const KFileItem &item : *itemList) {
2274             if (isItemVisible(item) && q->matchesMimeFilter(item)) {
2275                 oldVisibleItems.insert(item.name());
2276             }
2277         }
2278     }
2279 
2280     settings = newSettings;
2281 
2282     const QList<QUrl> dirs = lstDirs;
2283     for (const QUrl &dir : dirs) {
2284         KFileItemList deletedItems;
2285 
2286         const QList<KFileItem> *itemList = kDirListerCache()->itemsForDir(dir);
2287         if (!itemList) {
2288             continue;
2289         }
2290 
2291         for (const auto &item : *itemList) {
2292             const QString text = item.text();
2293             if (text == QLatin1Char('.') || text == QLatin1String("..")) {
2294                 continue;
2295             }
2296             const bool wasVisible = oldVisibleItems.find(item.name()) != oldVisibleItems.cend();
2297             const bool nowVisible = isItemVisible(item) && q->matchesMimeFilter(item);
2298             if (nowVisible && !wasVisible) {
2299                 addNewItem(dir, item); // takes care of emitting newItem or itemsFilteredByMime
2300             } else if (!nowVisible && wasVisible) {
2301                 deletedItems.append(item);
2302             }
2303         }
2304         if (!deletedItems.isEmpty()) {
2305             Q_EMIT q->itemsDeleted(deletedItems);
2306         }
2307         emitItems();
2308     }
2309     oldSettings = settings;
2310 }
2311 
2312 void KCoreDirLister::updateDirectory(const QUrl &dirUrl)
2313 {
2314     kDirListerCache()->updateDirectory(dirUrl);
2315 }
2316 
2317 bool KCoreDirLister::isFinished() const
2318 {
2319     return d->complete;
2320 }
2321 
2322 KFileItem KCoreDirLister::rootItem() const
2323 {
2324     return d->rootFileItem;
2325 }
2326 
2327 KFileItem KCoreDirLister::findByUrl(const QUrl &url) const
2328 {
2329     return kDirListerCache()->findByUrl(this, url);
2330 }
2331 
2332 KFileItem KCoreDirLister::findByName(const QString &name) const
2333 {
2334     return kDirListerCache()->findByName(this, name);
2335 }
2336 
2337 // ================ public filter methods ================ //
2338 
2339 void KCoreDirLister::setNameFilter(const QString &nameFilter)
2340 {
2341     if (d->nameFilter == nameFilter) {
2342         return;
2343     }
2344 
2345     d->prepareForSettingsChange();
2346 
2347     d->settings.lstFilters.clear();
2348     d->nameFilter = nameFilter;
2349     // Split on white space
2350     const QStringList list = nameFilter.split(QLatin1Char(' '), Qt::SkipEmptyParts);
2351     for (const QString &filter : list) {
2352         d->settings.lstFilters.append(QRegularExpression(QRegularExpression::wildcardToRegularExpression(filter), QRegularExpression::CaseInsensitiveOption));
2353     }
2354 }
2355 
2356 QString KCoreDirLister::nameFilter() const
2357 {
2358     return d->nameFilter;
2359 }
2360 
2361 void KCoreDirLister::setMimeFilter(const QStringList &mimeFilter)
2362 {
2363     if (d->settings.mimeFilter == mimeFilter) {
2364         return;
2365     }
2366 
2367     d->prepareForSettingsChange();
2368     if (mimeFilter.contains(QLatin1String("application/octet-stream")) || mimeFilter.contains(QLatin1String("all/allfiles"))) { // all files
2369         d->settings.mimeFilter.clear();
2370     } else {
2371         d->settings.mimeFilter = mimeFilter;
2372     }
2373 }
2374 
2375 void KCoreDirLister::setMimeExcludeFilter(const QStringList &mimeExcludeFilter)
2376 {
2377     if (d->settings.mimeExcludeFilter == mimeExcludeFilter) {
2378         return;
2379     }
2380 
2381     d->prepareForSettingsChange();
2382     d->settings.mimeExcludeFilter = mimeExcludeFilter;
2383 }
2384 
2385 void KCoreDirLister::clearMimeFilter()
2386 {
2387     d->prepareForSettingsChange();
2388     d->settings.mimeFilter.clear();
2389     d->settings.mimeExcludeFilter.clear();
2390 }
2391 
2392 QStringList KCoreDirLister::mimeFilters() const
2393 {
2394     return d->settings.mimeFilter;
2395 }
2396 
2397 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 94)
2398 bool KCoreDirLister::matchesFilter(const QString &name) const
2399 {
2400     return d->matchesFilter(name);
2401 }
2402 #endif
2403 
2404 bool KCoreDirListerPrivate::matchesFilter(const QString &name) const
2405 {
2406     return std::any_of(settings.lstFilters.cbegin(), settings.lstFilters.cend(), [&name](const QRegularExpression &filter) {
2407         return filter.match(name).hasMatch();
2408     });
2409 }
2410 
2411 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 94)
2412 bool KCoreDirLister::matchesMimeFilter(const QString &mime) const
2413 {
2414     return d->matchesMimeFilter(mime);
2415 }
2416 #endif
2417 
2418 bool KCoreDirListerPrivate::matchesMimeFilter(const QString &mime) const
2419 {
2420     return q->doMimeFilter(mime, settings.mimeFilter) && doMimeExcludeFilter(mime, settings.mimeExcludeFilter);
2421 }
2422 
2423 // ================ protected methods ================ //
2424 
2425 bool KCoreDirLister::matchesFilter(const KFileItem &item) const
2426 {
2427     Q_ASSERT(!item.isNull());
2428 
2429     if (item.text() == QLatin1String("..")) {
2430         return false;
2431     }
2432 
2433     if (!d->settings.isShowingDotFiles && item.isHidden()) {
2434         return false;
2435     }
2436 
2437     if (item.isDir() || d->settings.lstFilters.isEmpty()) {
2438         return true;
2439     }
2440 
2441     return d->matchesFilter(item.text());
2442 }
2443 
2444 bool KCoreDirLister::matchesMimeFilter(const KFileItem &item) const
2445 {
2446     Q_ASSERT(!item.isNull());
2447     // Don't lose time determining the MIME type if there is no filter
2448     if (d->settings.mimeFilter.isEmpty() && d->settings.mimeExcludeFilter.isEmpty()) {
2449         return true;
2450     }
2451     return d->matchesMimeFilter(item.mimetype());
2452 }
2453 
2454 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 90)
2455 bool KCoreDirLister::doNameFilter(const QString &name, const QList<QRegExp> &filters) const
2456 {
2457     return std::any_of(filters.cbegin(), filters.cend(), [&name](const QRegExp &filter) {
2458         return filter.exactMatch(name);
2459     });
2460 }
2461 #endif
2462 
2463 bool KCoreDirLister::doMimeFilter(const QString &mime, const QStringList &filters) const
2464 {
2465     if (filters.isEmpty()) {
2466         return true;
2467     }
2468 
2469     QMimeDatabase db;
2470     const QMimeType mimeptr = db.mimeTypeForName(mime);
2471     if (!mimeptr.isValid()) {
2472         return false;
2473     }
2474 
2475     qCDebug(KIO_CORE_DIRLISTER) << "doMimeFilter: investigating:" << mimeptr.name();
2476     return std::any_of(filters.cbegin(), filters.cend(), [&mimeptr](const QString &filter) {
2477         return mimeptr.inherits(filter);
2478     });
2479 }
2480 
2481 bool KCoreDirListerPrivate::doMimeExcludeFilter(const QString &mime, const QStringList &filters) const
2482 {
2483     return !std::any_of(filters.cbegin(), filters.cend(), [&mime](const QString &filter) {
2484         return mime == filter;
2485     });
2486 }
2487 
2488 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 82)
2489 void KCoreDirLister::handleError(KIO::Job *job)
2490 {
2491     qCWarning(KIO_CORE) << job->errorString();
2492 }
2493 #endif
2494 
2495 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 81)
2496 void KCoreDirLister::handleErrorMessage(const QString &message)
2497 {
2498     qCWarning(KIO_CORE) << message;
2499 }
2500 #endif
2501 
2502 // ================= private methods ================= //
2503 
2504 void KCoreDirListerPrivate::addNewItem(const QUrl &directoryUrl, const KFileItem &item)
2505 {
2506     if (!isItemVisible(item)) {
2507         return; // No reason to continue... bailing out here prevents a MIME type scan.
2508     }
2509 
2510     qCDebug(KIO_CORE_DIRLISTER) << "in" << directoryUrl << "item:" << item.url();
2511 
2512     if (q->matchesMimeFilter(item)) {
2513         Q_ASSERT(!item.isNull());
2514         lstNewItems[directoryUrl].append(item); // items not filtered
2515     } else {
2516         Q_ASSERT(!item.isNull());
2517         lstMimeFilteredItems.append(item); // only filtered by MIME type
2518     }
2519 }
2520 
2521 void KCoreDirListerPrivate::addNewItems(const QUrl &directoryUrl, const QList<KFileItem> &items)
2522 {
2523     // TODO: make this faster - test if we have a filter at all first
2524     // DF: was this profiled? The matchesFoo() functions should be fast, w/o filters...
2525     // Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good.
2526     for (const auto &item : items) {
2527         addNewItem(directoryUrl, item);
2528     }
2529 }
2530 
2531 void KCoreDirListerPrivate::addRefreshItem(const QUrl &directoryUrl, const KFileItem &oldItem, const KFileItem &item)
2532 {
2533     // Refreshing the root item "." of a dirlister
2534     if (directoryUrl == item.url()) {
2535         lstRefreshItems.append({oldItem, item});
2536         return;
2537     }
2538 
2539     const bool refreshItemWasFiltered = !isItemVisible(oldItem) || !q->matchesMimeFilter(oldItem);
2540     if (isItemVisible(item) && q->matchesMimeFilter(item)) {
2541         if (refreshItemWasFiltered) {
2542             Q_ASSERT(!item.isNull());
2543             lstNewItems[directoryUrl].append(item);
2544         } else {
2545             Q_ASSERT(!item.isNull());
2546             lstRefreshItems.append(qMakePair(oldItem, item));
2547         }
2548     } else if (!refreshItemWasFiltered) {
2549         // notify the user that the MIME type of a file changed that doesn't match
2550         // a filter or does match an exclude filter
2551         // This also happens when renaming foo to .foo and dot files are hidden (#174721)
2552         Q_ASSERT(!oldItem.isNull());
2553         lstRemoveItems.append(oldItem);
2554     }
2555 }
2556 
2557 void KCoreDirListerPrivate::emitItems()
2558 {
2559     if (!lstNewItems.empty()) {
2560         for (auto it = lstNewItems.cbegin(); it != lstNewItems.cend(); ++it) {
2561             const auto &val = it.value();
2562             Q_EMIT q->itemsAdded(it.key(), val);
2563             Q_EMIT q->newItems(val); // compat
2564         }
2565         lstNewItems.clear();
2566     }
2567 
2568     if (!lstMimeFilteredItems.empty()) {
2569 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100)
2570         Q_EMIT q->itemsFilteredByMime(lstMimeFilteredItems);
2571 #endif
2572         lstMimeFilteredItems.clear();
2573     }
2574 
2575     if (!lstRefreshItems.empty()) {
2576         Q_EMIT q->refreshItems(lstRefreshItems);
2577         lstRefreshItems.clear();
2578     }
2579 
2580     if (!lstRemoveItems.empty()) {
2581         Q_EMIT q->itemsDeleted(lstRemoveItems);
2582         lstRemoveItems.clear();
2583     }
2584 }
2585 
2586 bool KCoreDirListerPrivate::isItemVisible(const KFileItem &item) const
2587 {
2588     // Note that this doesn't include MIME type filters, because
2589     // of the itemsFilteredByMime signal. Filtered-by-MIME-type items are
2590     // considered "visible", they are just visible via a different signal...
2591     return (!settings.dirOnlyMode || item.isDir()) && q->matchesFilter(item);
2592 }
2593 
2594 void KCoreDirListerPrivate::emitItemsDeleted(const KFileItemList &itemsList)
2595 {
2596     KFileItemList items;
2597     std::copy_if(itemsList.cbegin(), itemsList.cend(), std::back_inserter(items), [this](const KFileItem &item) {
2598         return isItemVisible(item) || q->matchesMimeFilter(item);
2599     });
2600     if (!items.isEmpty()) {
2601         Q_EMIT q->itemsDeleted(items);
2602     }
2603 }
2604 
2605 KCoreDirListerPrivate::KCoreDirListerPrivate(KCoreDirLister *qq)
2606     : q(qq)
2607 {
2608 }
2609 
2610 // ================ private slots ================ //
2611 
2612 void KCoreDirListerPrivate::slotInfoMessage(KJob *, const QString &message)
2613 {
2614     Q_EMIT q->infoMessage(message);
2615 }
2616 
2617 void KCoreDirListerPrivate::slotPercent(KJob *job, unsigned long pcnt)
2618 {
2619     jobData[static_cast<KIO::ListJob *>(job)].percent = pcnt;
2620 
2621     int result = 0;
2622 
2623     KIO::filesize_t size = 0;
2624 
2625     for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) {
2626         const JobData &data = dataIt.value();
2627         result += data.percent * data.totalSize;
2628         size += data.totalSize;
2629     }
2630 
2631     if (size != 0) {
2632         result /= size;
2633     } else {
2634         result = 100;
2635     }
2636     Q_EMIT q->percent(result);
2637 }
2638 
2639 void KCoreDirListerPrivate::slotTotalSize(KJob *job, qulonglong size)
2640 {
2641     jobData[static_cast<KIO::ListJob *>(job)].totalSize = size;
2642 
2643     KIO::filesize_t result = 0;
2644     for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) {
2645         result += dataIt.value().totalSize;
2646     }
2647 
2648     Q_EMIT q->totalSize(result);
2649 }
2650 
2651 void KCoreDirListerPrivate::slotProcessedSize(KJob *job, qulonglong size)
2652 {
2653     jobData[static_cast<KIO::ListJob *>(job)].processedSize = size;
2654 
2655     KIO::filesize_t result = 0;
2656     for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) {
2657         result += dataIt.value().processedSize;
2658     }
2659 
2660     Q_EMIT q->processedSize(result);
2661 }
2662 
2663 void KCoreDirListerPrivate::slotSpeed(KJob *job, unsigned long spd)
2664 {
2665     jobData[static_cast<KIO::ListJob *>(job)].speed = spd;
2666 
2667     int result = 0;
2668     for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) {
2669         result += dataIt.value().speed;
2670     }
2671 
2672     Q_EMIT q->speed(result);
2673 }
2674 
2675 uint KCoreDirListerPrivate::numJobs()
2676 {
2677 #ifdef DEBUG_CACHE
2678     // This code helps detecting stale entries in the jobData map.
2679     qCDebug(KIO_CORE_DIRLISTER) << q << "numJobs:" << jobData.count();
2680     for (auto it = jobData.cbegin(); it != jobData.cend(); ++it) {
2681         qCDebug(KIO_CORE_DIRLISTER) << (void *)it.key();
2682         qCDebug(KIO_CORE_DIRLISTER) << it.key();
2683     }
2684 #endif
2685 
2686     return jobData.count();
2687 }
2688 
2689 void KCoreDirListerPrivate::jobDone(KIO::ListJob *job)
2690 {
2691     jobData.remove(job);
2692 }
2693 
2694 void KCoreDirLister::jobStarted(KIO::ListJob *job)
2695 {
2696     KCoreDirListerPrivate::JobData data;
2697     data.speed = 0;
2698     data.percent = 0;
2699     data.processedSize = 0;
2700     data.totalSize = 0;
2701 
2702     d->jobData.insert(job, data);
2703     d->complete = false;
2704 }
2705 
2706 void KCoreDirListerPrivate::connectJob(KIO::ListJob *job)
2707 {
2708     q->connect(job, &KJob::infoMessage, q, [this](KJob *job, const QString &plain) {
2709         slotInfoMessage(job, plain);
2710     });
2711 
2712     q->connect(job, &KJob::percentChanged, q, [this](KJob *job, ulong _percent) {
2713         slotPercent(job, _percent);
2714     });
2715 
2716     q->connect(job, &KJob::totalSize, q, [this](KJob *job, qulonglong _size) {
2717         slotTotalSize(job, _size);
2718     });
2719     q->connect(job, &KJob::processedSize, q, [this](KJob *job, qulonglong _psize) {
2720         slotProcessedSize(job, _psize);
2721     });
2722     q->connect(job, &KJob::speed, q, [this](KJob *job, qulonglong _speed) {
2723         slotSpeed(job, _speed);
2724     });
2725 }
2726 
2727 KFileItemList KCoreDirLister::items(WhichItems which) const
2728 {
2729     return itemsForDir(url(), which);
2730 }
2731 
2732 KFileItemList KCoreDirLister::itemsForDir(const QUrl &dir, WhichItems which) const
2733 {
2734     QList<KFileItem> *allItems = kDirListerCache()->itemsForDir(dir);
2735     KFileItemList result;
2736     if (!allItems) {
2737         return result;
2738     }
2739 
2740     if (which == AllItems) {
2741         return KFileItemList(*allItems);
2742     } else { // only items passing the filters
2743         std::copy_if(allItems->cbegin(), allItems->cend(), std::back_inserter(result), [this](const KFileItem &item) {
2744             return d->isItemVisible(item) && matchesMimeFilter(item);
2745         });
2746     }
2747     return result;
2748 }
2749 
2750 bool KCoreDirLister::delayedMimeTypes() const
2751 {
2752     return d->delayedMimeTypes;
2753 }
2754 
2755 void KCoreDirLister::setDelayedMimeTypes(bool delayedMimeTypes)
2756 {
2757     d->delayedMimeTypes = delayedMimeTypes;
2758 }
2759 
2760 // called by KCoreDirListerCache::slotRedirection
2761 void KCoreDirListerPrivate::redirect(const QUrl &oldUrl, const QUrl &newUrl, bool keepItems)
2762 {
2763     if (url.matches(oldUrl, QUrl::StripTrailingSlash)) {
2764         if (!keepItems) {
2765             rootFileItem = KFileItem();
2766         } else {
2767             rootFileItem.setUrl(newUrl);
2768         }
2769         url = newUrl;
2770     }
2771 
2772     const int idx = lstDirs.indexOf(oldUrl);
2773     if (idx == -1) {
2774         qCWarning(KIO_CORE) << "Unexpected redirection from" << oldUrl << "to" << newUrl << "but this dirlister is currently listing/holding" << lstDirs;
2775     } else {
2776         lstDirs[idx] = newUrl;
2777     }
2778 
2779     if (lstDirs.count() == 1) {
2780         if (!keepItems) {
2781             Q_EMIT q->clear();
2782         }
2783 
2784 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 80)
2785         Q_EMIT q->redirection(newUrl);
2786 #endif
2787 
2788     } else {
2789         if (!keepItems) {
2790 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
2791             Q_EMIT q->clear(oldUrl);
2792 #endif
2793             Q_EMIT q->clearDir(oldUrl);
2794         }
2795     }
2796     Q_EMIT q->redirection(oldUrl, newUrl);
2797 }
2798 
2799 void KCoreDirListerCacheDirectoryData::moveListersWithoutCachedItemsJob(const QUrl &url)
2800 {
2801     // Move dirlisters from listersCurrentlyListing to listersCurrentlyHolding,
2802     // but not those that are still waiting on a CachedItemsJob...
2803     // Unit-testing note:
2804     // Run kdirmodeltest in valgrind to hit the case where an update
2805     // is triggered while a lister has a CachedItemsJob (different timing...)
2806     QMutableListIterator<KCoreDirLister *> lister_it(listersCurrentlyListing);
2807     while (lister_it.hasNext()) {
2808         KCoreDirLister *kdl = lister_it.next();
2809         if (!kdl->d->cachedItemsJobForUrl(url)) {
2810             // OK, move this lister from "currently listing" to "currently holding".
2811 
2812             // Huh? The KCoreDirLister was present twice in listersCurrentlyListing, or was in both lists?
2813             Q_ASSERT(!listersCurrentlyHolding.contains(kdl));
2814             if (!listersCurrentlyHolding.contains(kdl)) {
2815                 listersCurrentlyHolding.append(kdl);
2816             }
2817             lister_it.remove();
2818         } else {
2819             qCDebug(KIO_CORE_DIRLISTER) << "Not moving" << kdl << "to listersCurrentlyHolding because it still has job" << kdl->d->m_cachedItemsJobs;
2820         }
2821     }
2822 }
2823 
2824 KFileItem KCoreDirLister::cachedItemForUrl(const QUrl &url)
2825 {
2826     if (kDirListerCache.exists()) {
2827         return kDirListerCache()->itemForUrl(url);
2828     } else {
2829         return {};
2830     }
2831 }
2832 
2833 bool KCoreDirLister::autoErrorHandlingEnabled() const
2834 {
2835     return d->m_autoErrorHandling;
2836 }
2837 
2838 void KCoreDirLister::setAutoErrorHandlingEnabled(bool enable)
2839 {
2840     d->m_autoErrorHandling = enable;
2841 }
2842 
2843 KCoreDirListerCache::CacheHiddenFile *KCoreDirListerCache::cachedDotHiddenForDir(const QString &dir)
2844 {
2845     const QString path = dir + QLatin1String("/.hidden");
2846     QFile dotHiddenFile(path);
2847 
2848     if (dotHiddenFile.exists()) {
2849         const QDateTime mtime = QFileInfo(dotHiddenFile).lastModified();
2850         CacheHiddenFile *cachedDotHiddenFile = m_cacheHiddenFiles.object(path);
2851 
2852         if (cachedDotHiddenFile && mtime <= cachedDotHiddenFile->mtime) {
2853             // ".hidden" is in cache and still valid (the file was not modified since then),
2854             // so return it
2855             return cachedDotHiddenFile;
2856         } else {
2857             // read the ".hidden" file, then cache it and return it
2858             if (dotHiddenFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
2859                 std::set<QString> filesToHide;
2860                 QTextStream stream(&dotHiddenFile);
2861                 while (!stream.atEnd()) {
2862                     QString name = stream.readLine();
2863                     if (!name.isEmpty()) {
2864                         filesToHide.insert(name);
2865                     }
2866                 }
2867 
2868                 m_cacheHiddenFiles.insert(path, new CacheHiddenFile(mtime, std::move(filesToHide)));
2869 
2870                 return m_cacheHiddenFiles.object(path);
2871             }
2872         }
2873     }
2874 
2875     return {};
2876 }
2877 
2878 #include "moc_kcoredirlister.cpp"
2879 #include "moc_kcoredirlister_p.cpp"