File indexing completed on 2023-09-24 04:08:35

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