File indexing completed on 2024-04-21 03:55:05

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