1 /*
2     This file is part of the KDE project
3     SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
4     SPDX-FileCopyrightText: 2000 Carsten Pfeiffer <pfeiffer@kde.org>
5     SPDX-FileCopyrightText: 2003-2005 David Faure <faure@kde.org>
6     SPDX-FileCopyrightText: 2001-2006 Michael Brade <brade@kde.org>
7 
8     SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10 
11 #include "kcoredirlister.h"
12 #include "kcoredirlister_p.h"
13 
14 #include "../pathhelpers_p.h"
15 #include "kiocoredebug.h"
16 #include "kmountpoint.h"
17 #include "kprotocolmanager.h"
18 #include <KJobUiDelegate>
19 #include <kio/listjob.h>
20 
21 #include <KLocalizedString>
22 
23 #include <QDir>
24 #include <QFile>
25 #include <QFileInfo>
26 #include <QMimeDatabase>
27 #include <QRegularExpression>
28 #include <QTextStream>
29 
30 #include <list>
31 
32 #include <QLoggingCategory>
33 Q_DECLARE_LOGGING_CATEGORY(KIO_CORE_DIRLISTER)
34 Q_LOGGING_CATEGORY(KIO_CORE_DIRLISTER, "kf.kio.core.dirlister", QtWarningMsg)
35 
36 // Enable this to get printDebug() called often, to see the contents of the cache
37 //#define DEBUG_CACHE
38 
39 // Make really sure it doesn't get activated in the final build
40 #ifdef NDEBUG
41 #undef DEBUG_CACHE
42 #endif
43 
Q_GLOBAL_STATIC(KCoreDirListerCache,kDirListerCache)44 Q_GLOBAL_STATIC(KCoreDirListerCache, kDirListerCache)
45 
46 KCoreDirListerCache::KCoreDirListerCache()
47     : itemsCached(10)
48     , // keep the last 10 directories around
49     m_cacheHiddenFiles(10) // keep the last 10 ".hidden" files around
50 {
51     qCDebug(KIO_CORE_DIRLISTER);
52 
53     connect(&pendingUpdateTimer, &QTimer::timeout, this, &KCoreDirListerCache::processPendingUpdates);
54     pendingUpdateTimer.setSingleShot(true);
55 
56     connect(KDirWatch::self(), &KDirWatch::dirty, this, &KCoreDirListerCache::slotFileDirty);
57     connect(KDirWatch::self(), &KDirWatch::created, this, &KCoreDirListerCache::slotFileCreated);
58     connect(KDirWatch::self(), &KDirWatch::deleted, this, &KCoreDirListerCache::slotFileDeleted);
59 
60 #ifndef KIO_ANDROID_STUB
61     kdirnotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this);
62     connect(kdirnotify, &org::kde::KDirNotify::FileRenamedWithLocalPath, this, &KCoreDirListerCache::slotFileRenamed);
63     connect(kdirnotify, &org::kde::KDirNotify::FilesAdded, this, &KCoreDirListerCache::slotFilesAdded);
64     connect(kdirnotify, &org::kde::KDirNotify::FilesChanged, this, &KCoreDirListerCache::slotFilesChanged);
65     connect(kdirnotify, &org::kde::KDirNotify::FilesRemoved, this, qOverload<const QStringList &>(&KCoreDirListerCache::slotFilesRemoved));
66 #endif
67 
68     // Probably not needed in KF5 anymore:
69     // The use of KUrl::url() in ~DirItem (sendSignal) crashes if the static for QRegExpEngine got deleted already,
70     // so we need to destroy the KCoreDirListerCache before that.
71     // qAddPostRoutine(kDirListerCache.destroy);
72 }
73 
~KCoreDirListerCache()74 KCoreDirListerCache::~KCoreDirListerCache()
75 {
76     qCDebug(KIO_CORE_DIRLISTER);
77 
78     qDeleteAll(itemsInUse);
79     itemsInUse.clear();
80 
81     itemsCached.clear();
82     directoryData.clear();
83     m_cacheHiddenFiles.clear();
84 
85     if (KDirWatch::exists()) {
86         KDirWatch::self()->disconnect(this);
87     }
88 }
89 
90 // setting _reload to true will emit the old files and
91 // call updateDirectory
listDir(KCoreDirLister * lister,const QUrl & dirUrl,bool _keep,bool _reload)92 bool KCoreDirListerCache::listDir(KCoreDirLister *lister, const QUrl &dirUrl, bool _keep, bool _reload)
93 {
94     QUrl _url(dirUrl);
95     _url.setPath(QDir::cleanPath(_url.path())); // kill consecutive slashes
96 
97     // like this we don't have to worry about trailing slashes any further
98     _url = _url.adjusted(QUrl::StripTrailingSlash);
99 
100     QString resolved;
101     if (_url.isLocalFile()) {
102         // Resolve symlinks (#213799)
103         const QString local = _url.toLocalFile();
104         resolved = QFileInfo(local).canonicalFilePath();
105         if (local != resolved) {
106             canonicalUrls[QUrl::fromLocalFile(resolved)].append(_url);
107         }
108         // TODO: remove entry from canonicalUrls again in forgetDirs
109         // Note: this is why we use a QStringList value in there rather than a std::set:
110         // we can just remove one entry and not have to worry about other dirlisters
111         // (the non-unicity of the stringlist gives us the refcounting, basically).
112     }
113 
114     qCDebug(KIO_CORE_DIRLISTER) << lister << "url=" << _url << "keep=" << _keep << "reload=" << _reload;
115 #ifdef DEBUG_CACHE
116     printDebug();
117 #endif
118 
119     if (!_keep) {
120         // stop any running jobs for lister
121         stop(lister, true /*silent*/);
122 
123         // clear our internal list for lister
124         forgetDirs(lister);
125 
126         lister->d->rootFileItem = KFileItem();
127     } else if (lister->d->lstDirs.contains(_url)) {
128         // stop the job listing _url for this lister
129         stopListingUrl(lister, _url, true /*silent*/);
130 
131         // remove the _url as well, it will be added in a couple of lines again!
132         // forgetDirs with three args does not do this
133         // TODO: think about moving this into forgetDirs
134         lister->d->lstDirs.removeAll(_url);
135 
136         // clear _url for lister
137         forgetDirs(lister, _url, true);
138 
139         if (lister->d->url == _url) {
140             lister->d->rootFileItem = KFileItem();
141         }
142     }
143 
144     lister->d->complete = false;
145 
146     lister->d->lstDirs.append(_url);
147 
148     if (lister->d->url.isEmpty() || !_keep) { // set toplevel URL only if not set yet
149         lister->d->url = _url;
150     }
151 
152     DirItem *itemU = itemsInUse.value(_url);
153 
154     KCoreDirListerCacheDirectoryData &dirData = directoryData[_url]; // find or insert
155 
156     if (dirData.listersCurrentlyListing.isEmpty()) {
157         // if there is an update running for _url already we get into
158         // the following case - it will just be restarted by updateDirectory().
159 
160         dirData.listersCurrentlyListing.append(lister);
161 
162         DirItem *itemFromCache = nullptr;
163         if (itemU || (!_reload && (itemFromCache = itemsCached.take(_url)))) {
164             if (itemU) {
165                 qCDebug(KIO_CORE_DIRLISTER) << "Entry already in use:" << _url;
166                 // if _reload is set, then we'll emit cached items and then updateDirectory.
167             } else {
168                 qCDebug(KIO_CORE_DIRLISTER) << "Entry in cache:" << _url;
169                 itemsInUse.insert(_url, itemFromCache);
170                 itemU = itemFromCache;
171             }
172             if (lister->d->autoUpdate) {
173                 itemU->incAutoUpdate();
174             }
175             if (itemFromCache && itemFromCache->watchedWhileInCache) {
176                 itemFromCache->watchedWhileInCache = false;
177                 ;
178                 itemFromCache->decAutoUpdate();
179             }
180 
181             Q_EMIT lister->started(_url);
182 
183             // List items from the cache in a delayed manner, just like things would happen
184             // if we were not using the cache.
185             new KCoreDirListerPrivate::CachedItemsJob(lister, _url, _reload);
186 
187         } else {
188             // dir not in cache or _reload is true
189             if (_reload) {
190                 qCDebug(KIO_CORE_DIRLISTER) << "Reloading directory:" << _url;
191                 itemsCached.remove(_url);
192             } else {
193                 qCDebug(KIO_CORE_DIRLISTER) << "Listing directory:" << _url;
194             }
195 
196             itemU = new DirItem(_url, resolved);
197             itemsInUse.insert(_url, itemU);
198             if (lister->d->autoUpdate) {
199                 itemU->incAutoUpdate();
200             }
201 
202             KIO::ListJob *job = KIO::listDir(_url, KIO::HideProgressInfo);
203             if (lister->requestMimeTypeWhileListing()) {
204                 job->addMetaData(QStringLiteral("statDetails"), QString::number(KIO::StatDefaultDetails | KIO::StatMimeType));
205             }
206             runningListJobs.insert(job, KIO::UDSEntryList());
207 
208             lister->jobStarted(job);
209             lister->d->connectJob(job);
210 
211             connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotEntries);
212             connect(job, &KJob::result, this, &KCoreDirListerCache::slotResult);
213             connect(job, &KIO::ListJob::redirection, this, &KCoreDirListerCache::slotRedirection);
214 
215             Q_EMIT lister->started(_url);
216 
217             qCDebug(KIO_CORE_DIRLISTER) << "Entry now being listed by" << dirData.listersCurrentlyListing;
218         }
219     } else {
220         qCDebug(KIO_CORE_DIRLISTER) << "Entry currently being listed:" << _url << "by" << dirData.listersCurrentlyListing;
221 #ifdef DEBUG_CACHE
222         printDebug();
223 #endif
224 
225         Q_EMIT lister->started(_url);
226 
227         // Maybe listersCurrentlyListing/listersCurrentlyHolding should be QSets?
228         Q_ASSERT(!dirData.listersCurrentlyListing.contains(lister));
229         dirData.listersCurrentlyListing.append(lister);
230 
231         KIO::ListJob *job = jobForUrl(_url);
232         // job will be 0 if we were listing from cache rather than listing from a kio job.
233         if (job) {
234             lister->jobStarted(job);
235             lister->d->connectJob(job);
236         }
237         Q_ASSERT(itemU);
238 
239         // List existing items in a delayed manner, just like things would happen
240         // if we were not using the cache.
241         qCDebug(KIO_CORE_DIRLISTER) << "Listing" << itemU->lstItems.count() << "cached items soon";
242         auto *cachedItemsJob = new KCoreDirListerPrivate::CachedItemsJob(lister, _url, _reload);
243         if (job) {
244             // The ListJob will take care of emitting completed.
245             // ### If it finishes before the CachedItemsJob, then we'll emit cached items after completed(), not sure how bad this is.
246             cachedItemsJob->setEmitCompleted(false);
247         }
248 
249 #ifdef DEBUG_CACHE
250         printDebug();
251 #endif
252     }
253 
254     return true;
255 }
256 
cachedItemsJobForUrl(const QUrl & url) const257 KCoreDirListerPrivate::CachedItemsJob *KCoreDirListerPrivate::cachedItemsJobForUrl(const QUrl &url) const
258 {
259     for (CachedItemsJob *job : m_cachedItemsJobs) {
260         if (job->url() == url) {
261             return job;
262         }
263     }
264     return nullptr;
265 }
266 
CachedItemsJob(KCoreDirLister * lister,const QUrl & url,bool reload)267 KCoreDirListerPrivate::CachedItemsJob::CachedItemsJob(KCoreDirLister *lister, const QUrl &url, bool reload)
268     : KJob(lister)
269     , m_lister(lister)
270     , m_url(url)
271     , m_reload(reload)
272     , m_emitCompleted(true)
273 {
274     qCDebug(KIO_CORE_DIRLISTER) << "Creating CachedItemsJob" << this << "for lister" << lister << url;
275     if (lister->d->cachedItemsJobForUrl(url)) {
276         qCWarning(KIO_CORE) << "Lister" << lister << "has a cached items job already for" << url;
277     }
278     lister->d->m_cachedItemsJobs.append(this);
279     setAutoDelete(true);
280     start();
281 }
282 
283 // Called by start() via QueuedConnection
done()284 void KCoreDirListerPrivate::CachedItemsJob::done()
285 {
286     if (!m_lister) { // job was already killed, but waiting deletion due to deleteLater
287         return;
288     }
289     kDirListerCache()->emitItemsFromCache(this, m_lister, m_url, m_reload, m_emitCompleted);
290     emitResult();
291 }
292 
doKill()293 bool KCoreDirListerPrivate::CachedItemsJob::doKill()
294 {
295     qCDebug(KIO_CORE_DIRLISTER) << this;
296     kDirListerCache()->forgetCachedItemsJob(this, m_lister, m_url);
297     if (!property("_kdlc_silent").toBool()) {
298 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
299         Q_EMIT m_lister->canceled(m_url);
300 #endif
301         Q_EMIT m_lister->listingDirCanceled(m_url);
302 
303         Q_EMIT m_lister->canceled();
304     }
305     m_lister = nullptr;
306     return true;
307 }
308 
emitItemsFromCache(KCoreDirListerPrivate::CachedItemsJob * cachedItemsJob,KCoreDirLister * lister,const QUrl & _url,bool _reload,bool _emitCompleted)309 void KCoreDirListerCache::emitItemsFromCache(KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob,
310                                              KCoreDirLister *lister,
311                                              const QUrl &_url,
312                                              bool _reload,
313                                              bool _emitCompleted)
314 {
315     lister->d->complete = false;
316 
317     DirItem *itemU = kDirListerCache()->itemsInUse.value(_url);
318     if (!itemU) {
319         qCWarning(KIO_CORE) << "Can't find item for directory" << _url << "anymore";
320     } else {
321         const QList<KFileItem> items = itemU->lstItems;
322         const KFileItem rootItem = itemU->rootItem;
323         _reload = _reload || !itemU->complete;
324 
325         if (lister->d->rootFileItem.isNull() && !rootItem.isNull() && lister->d->url == _url) {
326             lister->d->rootFileItem = rootItem;
327         }
328         if (!items.isEmpty()) {
329             qCDebug(KIO_CORE_DIRLISTER) << "emitting" << items.count() << "for lister" << lister;
330             lister->d->addNewItems(_url, items);
331             lister->d->emitItems();
332         }
333     }
334 
335     forgetCachedItemsJob(cachedItemsJob, lister, _url);
336 
337     // Emit completed, unless we were told not to,
338     // or if listDir() was called while another directory listing for this dir was happening,
339     // so we "joined" it. We detect that using jobForUrl to ensure it's a real ListJob,
340     // not just a lister-specific CachedItemsJob (which wouldn't emit completed for us).
341     if (_emitCompleted) {
342         lister->d->complete = true;
343 
344 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
345         Q_EMIT lister->completed(_url);
346 #endif
347         Q_EMIT lister->listingDirCompleted(_url);
348         Q_EMIT lister->completed();
349 
350         if (_reload) {
351             updateDirectory(_url);
352         }
353     }
354 }
355 
forgetCachedItemsJob(KCoreDirListerPrivate::CachedItemsJob * cachedItemsJob,KCoreDirLister * lister,const QUrl & _url)356 void KCoreDirListerCache::forgetCachedItemsJob(KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob, KCoreDirLister *lister, const QUrl &_url)
357 {
358     // Modifications to data structures only below this point;
359     // so that addNewItems is called with a consistent state
360 
361     lister->d->m_cachedItemsJobs.removeAll(cachedItemsJob);
362 
363     KCoreDirListerCacheDirectoryData &dirData = directoryData[_url];
364     Q_ASSERT(dirData.listersCurrentlyListing.contains(lister));
365 
366     KIO::ListJob *listJob = jobForUrl(_url);
367     if (!listJob) {
368         Q_ASSERT(!dirData.listersCurrentlyHolding.contains(lister));
369         qCDebug(KIO_CORE_DIRLISTER) << "Moving from listing to holding, because no more job" << lister << _url;
370         dirData.listersCurrentlyHolding.append(lister);
371         dirData.listersCurrentlyListing.removeAll(lister);
372     } else {
373         qCDebug(KIO_CORE_DIRLISTER) << "Still having a listjob" << listJob << ", so not moving to currently-holding.";
374     }
375 }
376 
stop(KCoreDirLister * lister,bool silent)377 void KCoreDirListerCache::stop(KCoreDirLister *lister, bool silent)
378 {
379     qCDebug(KIO_CORE_DIRLISTER) << "lister:" << lister << "silent=" << silent;
380 
381     const QList<QUrl> urls = lister->d->lstDirs;
382     for (const QUrl &url : urls) {
383         stopListingUrl(lister, url, silent);
384     }
385 }
386 
stopListingUrl(KCoreDirLister * lister,const QUrl & _u,bool silent)387 void KCoreDirListerCache::stopListingUrl(KCoreDirLister *lister, const QUrl &_u, bool silent)
388 {
389     QUrl url(_u);
390     url = url.adjusted(QUrl::StripTrailingSlash);
391 
392     KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob = lister->d->cachedItemsJobForUrl(url);
393     if (cachedItemsJob) {
394         if (silent) {
395             cachedItemsJob->setProperty("_kdlc_silent", true);
396         }
397         cachedItemsJob->kill(); // removes job from list, too
398     }
399 
400     // TODO: consider to stop all the "child jobs" of url as well
401     qCDebug(KIO_CORE_DIRLISTER) << lister << " url=" << url;
402 
403     const auto dirit = directoryData.find(url);
404     if (dirit == directoryData.end()) {
405         return;
406     }
407     KCoreDirListerCacheDirectoryData &dirData = dirit.value();
408     if (dirData.listersCurrentlyListing.contains(lister)) {
409         qCDebug(KIO_CORE_DIRLISTER) << " found lister" << lister << "in list - for" << url;
410         if (dirData.listersCurrentlyListing.count() == 1) {
411             // This was the only dirlister interested in the list job -> kill the job
412             stopListJob(url, silent);
413         } else {
414             // Leave the job running for the other dirlisters, just unsubscribe us.
415             dirData.listersCurrentlyListing.removeAll(lister);
416             if (!silent) {
417                 Q_EMIT lister->canceled();
418 
419 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
420                 Q_EMIT lister->canceled(url);
421 #endif
422                 Q_EMIT lister->listingDirCanceled(url);
423             }
424         }
425     }
426 }
427 
428 // Helper for stop() and stopListingUrl()
stopListJob(const QUrl & url,bool silent)429 void KCoreDirListerCache::stopListJob(const QUrl &url, bool silent)
430 {
431     // Old idea: if it's an update job, let's just leave the job running.
432     // After all, update jobs do run for "listersCurrentlyHolding",
433     // so there's no reason to kill them just because @p lister is now a holder.
434 
435     // However it could be a long-running non-local job (e.g. filenamesearch), which
436     // the user wants to abort, and which will never be used for updating...
437     // And in any case slotEntries/slotResult is not meant to be called by update jobs.
438     // So, change of plan, let's kill it after all, in a way that triggers slotResult/slotUpdateResult.
439 
440     KIO::ListJob *job = jobForUrl(url);
441     if (job) {
442         qCDebug(KIO_CORE_DIRLISTER) << "Killing list job" << job << "for" << url;
443         if (silent) {
444             job->setProperty("_kdlc_silent", true);
445         }
446         job->kill(KJob::EmitResult);
447     }
448 }
449 
setAutoUpdate(KCoreDirLister * lister,bool enable)450 void KCoreDirListerCache::setAutoUpdate(KCoreDirLister *lister, bool enable)
451 {
452     // IMPORTANT: this method does not check for the current autoUpdate state!
453 
454     for (auto it = lister->d->lstDirs.constBegin(), cend = lister->d->lstDirs.constEnd(); it != cend; ++it) {
455         DirItem *dirItem = itemsInUse.value(*it);
456         Q_ASSERT(dirItem);
457         if (enable) {
458             dirItem->incAutoUpdate();
459         } else {
460             dirItem->decAutoUpdate();
461         }
462     }
463 }
464 
forgetDirs(KCoreDirLister * lister)465 void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister)
466 {
467     qCDebug(KIO_CORE_DIRLISTER) << lister;
468 
469     Q_EMIT lister->clear();
470     // clear lister->d->lstDirs before calling forgetDirs(), so that
471     // it doesn't contain things that itemsInUse doesn't. When emitting
472     // the canceled signals, lstDirs must not contain anything that
473     // itemsInUse does not contain. (otherwise it might crash in findByName()).
474     const QList<QUrl> lstDirsCopy = lister->d->lstDirs;
475     lister->d->lstDirs.clear();
476 
477     qCDebug(KIO_CORE_DIRLISTER) << "Iterating over dirs" << lstDirsCopy;
478     for (const QUrl &dir : lstDirsCopy) {
479         forgetDirs(lister, dir, false);
480     }
481 }
482 
manually_mounted(const QString & path,const KMountPoint::List & possibleMountPoints)483 static bool manually_mounted(const QString &path, const KMountPoint::List &possibleMountPoints)
484 {
485     KMountPoint::Ptr mp = possibleMountPoints.findByPath(path);
486     if (!mp) { // not listed in fstab -> yes, manually mounted
487         if (possibleMountPoints.isEmpty()) { // no fstab at all -> don't assume anything
488             return false;
489         }
490         return true;
491     }
492     // noauto -> manually mounted. Otherwise, mounted at boot time, won't be unmounted any time soon hopefully.
493     return mp->mountOptions().contains(QLatin1String("noauto"));
494 }
495 
forgetDirs(KCoreDirLister * lister,const QUrl & _url,bool notify)496 void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister, const QUrl &_url, bool notify)
497 {
498     qCDebug(KIO_CORE_DIRLISTER) << lister << " _url: " << _url;
499 
500     const QUrl url = _url.adjusted(QUrl::StripTrailingSlash);
501 
502     DirectoryDataHash::iterator dit = directoryData.find(url);
503     if (dit == directoryData.end()) {
504         return;
505     }
506     KCoreDirListerCacheDirectoryData &dirData = *dit;
507     dirData.listersCurrentlyHolding.removeAll(lister);
508 
509     // This lister doesn't care for updates running in <url> anymore
510     KIO::ListJob *job = jobForUrl(url);
511     if (job) {
512         lister->d->jobDone(job);
513     }
514 
515     DirItem *item = itemsInUse.value(url);
516     Q_ASSERT(item);
517     bool insertIntoCache = false;
518 
519     if (dirData.listersCurrentlyHolding.isEmpty() && dirData.listersCurrentlyListing.isEmpty()) {
520         // item not in use anymore -> move into cache if complete
521         directoryData.erase(dit);
522         itemsInUse.remove(url);
523 
524         // this job is a running update which nobody cares about anymore
525         if (job) {
526             killJob(job);
527             qCDebug(KIO_CORE_DIRLISTER) << "Killing update job for " << url;
528 
529             // Well, the user of KCoreDirLister doesn't really care that we're stopping
530             // a background-running job from a previous URL (in listDir) -> commented out.
531             // stop() already emitted canceled.
532             // emit lister->canceled( url );
533             if (lister->d->numJobs() == 0) {
534                 lister->d->complete = true;
535                 // emit lister->canceled();
536             }
537         }
538 
539         if (notify) {
540             lister->d->lstDirs.removeAll(url);
541 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
542             Q_EMIT lister->clear(url);
543 #endif
544             Q_EMIT lister->clearDir(url);
545         }
546 
547         insertIntoCache = item->complete;
548         if (insertIntoCache) {
549             // TODO(afiestas): remove use of KMountPoint+manually_mounted and port to Solid:
550             // 1) find Volume for the local path "item->url.toLocalFile()" (which could be anywhere
551             // under the mount point) -- probably needs a new operator in libsolid query parser
552             // 2) [**] becomes: if (Drive is hotpluggable or Volume is removable) "set to dirty" else "keep watch"
553             const KMountPoint::List possibleMountPoints = KMountPoint::possibleMountPoints(KMountPoint::NeedMountOptions);
554 
555             // Should we forget the dir for good, or keep a watch on it?
556             // Generally keep a watch, except when it would prevent
557             // unmounting a removable device (#37780)
558             const bool isLocal = item->url.isLocalFile();
559             bool isManuallyMounted = false;
560             bool containsManuallyMounted = false;
561             if (isLocal) {
562                 isManuallyMounted = manually_mounted(item->url.toLocalFile(), possibleMountPoints);
563                 if (!isManuallyMounted) {
564                     // Look for a manually-mounted directory inside
565                     // If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM
566                     // I hope this isn't too slow
567                     auto kit = item->lstItems.constBegin();
568                     const auto kend = item->lstItems.constEnd();
569                     for (; kit != kend && !containsManuallyMounted; ++kit) {
570                         if ((*kit).isDir() && manually_mounted((*kit).url().toLocalFile(), possibleMountPoints)) {
571                             containsManuallyMounted = true;
572                         }
573                     }
574                 }
575             }
576 
577             if (isManuallyMounted || containsManuallyMounted) { // [**]
578                 qCDebug(KIO_CORE_DIRLISTER) << "Not adding a watch on " << item->url << " because it "
579                                             << (isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir");
580                 item->complete = false; // set to "dirty"
581             } else {
582                 item->incAutoUpdate(); // keep watch
583                 item->watchedWhileInCache = true;
584             }
585         } else {
586             delete item;
587             item = nullptr;
588         }
589     }
590 
591     if (item && lister->d->autoUpdate) {
592         item->decAutoUpdate();
593     }
594 
595     // Inserting into QCache must be done last, since it might delete the item
596     if (item && insertIntoCache) {
597         qCDebug(KIO_CORE_DIRLISTER) << lister << "item moved into cache:" << url;
598         itemsCached.insert(url, item);
599     }
600 }
601 
updateDirectory(const QUrl & _dir)602 void KCoreDirListerCache::updateDirectory(const QUrl &_dir)
603 {
604     qCDebug(KIO_CORE_DIRLISTER) << _dir;
605 
606     const QUrl dir = _dir.adjusted(QUrl::StripTrailingSlash);
607     if (!checkUpdate(dir)) {
608         return;
609     }
610 
611     // A job can be running to
612     //   - only list a new directory: the listers are in listersCurrentlyListing
613     //   - only update a directory: the listers are in listersCurrentlyHolding
614     //   - update a currently running listing: the listers are in both
615 
616     KCoreDirListerCacheDirectoryData &dirData = directoryData[dir];
617     const QList<KCoreDirLister *> listers = dirData.listersCurrentlyListing;
618     const QList<KCoreDirLister *> holders = dirData.listersCurrentlyHolding;
619 
620     qCDebug(KIO_CORE_DIRLISTER) << dir << "listers=" << listers << "holders=" << holders;
621 
622     bool killed = false;
623     KIO::ListJob *job = jobForUrl(dir);
624     if (job) {
625         // the job is running already, tell it to do another update at the end
626         // (don't kill it, we would keep doing that during a long download to a slow sshfs mount)
627         job->setProperty("need_another_update", true);
628         return;
629     } else {
630         // Emit any cached items.
631         // updateDirectory() is about the diff compared to the cached items...
632         for (const KCoreDirLister *kdl : listers) {
633             KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob = kdl->d->cachedItemsJobForUrl(dir);
634             if (cachedItemsJob) {
635                 cachedItemsJob->setEmitCompleted(false);
636                 cachedItemsJob->done(); // removes from cachedItemsJobs list
637                 delete cachedItemsJob;
638                 killed = true;
639             }
640         }
641     }
642     qCDebug(KIO_CORE_DIRLISTER) << "Killed=" << killed;
643 
644     // we don't need to emit canceled signals since we only replaced the job,
645     // the listing is continuing.
646 
647     if (!(listers.isEmpty() || killed)) {
648         qCWarning(KIO_CORE) << "The unexpected happened.";
649         qCWarning(KIO_CORE) << "listers for" << dir << "=" << listers;
650         qCWarning(KIO_CORE) << "job=" << job;
651         for (const KCoreDirLister *kdl : listers) {
652             qCDebug(KIO_CORE_DIRLISTER) << "lister" << kdl << "m_cachedItemsJobs=" << kdl->d->m_cachedItemsJobs;
653         }
654 #ifndef NDEBUG
655         printDebug();
656 #endif
657     }
658     Q_ASSERT(listers.isEmpty() || killed);
659 
660     bool requestMimeType = std::any_of(listers.begin(), listers.end(), [](KCoreDirLister *lister) {
661         return lister->requestMimeTypeWhileListing();
662     });
663     requestMimeType = requestMimeType || std::any_of(holders.begin(), holders.end(), [](KCoreDirLister *lister) {
664                           return lister->requestMimeTypeWhileListing();
665                       });
666 
667     job = KIO::listDir(dir, KIO::HideProgressInfo);
668     runningListJobs.insert(job, KIO::UDSEntryList());
669 
670     if (requestMimeType) {
671         job->addMetaData(QStringLiteral("statDetails"), QString::number(KIO::StatDefaultDetails | KIO::StatMimeType));
672     }
673 
674     connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotUpdateEntries);
675     connect(job, &KJob::result, this, &KCoreDirListerCache::slotUpdateResult);
676 
677     qCDebug(KIO_CORE_DIRLISTER) << "update started in" << dir;
678 
679     for (KCoreDirLister *kdl : listers) {
680         kdl->jobStarted(job);
681     }
682 
683     if (!holders.isEmpty()) {
684         if (!killed) {
685             for (KCoreDirLister *kdl : holders) {
686                 kdl->jobStarted(job);
687                 Q_EMIT kdl->started(dir);
688             }
689         } else {
690             for (KCoreDirLister *kdl : holders) {
691                 kdl->jobStarted(job);
692             }
693         }
694     }
695 }
696 
checkUpdate(const QUrl & _dir)697 bool KCoreDirListerCache::checkUpdate(const QUrl &_dir)
698 {
699     if (!itemsInUse.contains(_dir)) {
700         DirItem *item = itemsCached[_dir];
701         if (item && item->complete) {
702             item->complete = false;
703             item->watchedWhileInCache = false;
704             item->decAutoUpdate();
705             qCDebug(KIO_CORE_DIRLISTER) << "directory " << _dir << " not in use, marked dirty.";
706         }
707         // else
708         qCDebug(KIO_CORE_DIRLISTER) << "aborted, directory " << _dir << " not in cache.";
709         return false;
710     } else {
711         return true;
712     }
713 }
714 
itemForUrl(const QUrl & url) const715 KFileItem KCoreDirListerCache::itemForUrl(const QUrl &url) const
716 {
717     return findByUrl(nullptr, url);
718 }
719 
dirItemForUrl(const QUrl & dir) const720 KCoreDirListerCache::DirItem *KCoreDirListerCache::dirItemForUrl(const QUrl &dir) const
721 {
722     const QUrl url = dir.adjusted(QUrl::StripTrailingSlash);
723     DirItem *item = itemsInUse.value(url);
724     if (!item) {
725         item = itemsCached[url];
726     }
727     return item;
728 }
729 
itemsForDir(const QUrl & dir) const730 QList<KFileItem> *KCoreDirListerCache::itemsForDir(const QUrl &dir) const
731 {
732     DirItem *item = dirItemForUrl(dir);
733     return item ? &item->lstItems : nullptr;
734 }
735 
findByName(const KCoreDirLister * lister,const QString & _name) const736 KFileItem KCoreDirListerCache::findByName(const KCoreDirLister *lister, const QString &_name) const
737 {
738     Q_ASSERT(lister);
739 
740     auto isMatch = [&_name](const KFileItem &item) {
741         return _name == item.name();
742     };
743 
744     for (const auto &dirUrl : std::as_const(lister->d->lstDirs)) {
745         DirItem *dirItem = itemsInUse.value(dirUrl);
746         Q_ASSERT(dirItem);
747 
748         auto it = std::find_if(dirItem->lstItems.cbegin(), dirItem->lstItems.cend(), isMatch);
749         if (it != dirItem->lstItems.cend()) {
750             return *it;
751         }
752     }
753 
754     return {};
755 }
756 
findByUrl(const KCoreDirLister * lister,const QUrl & _u) const757 KFileItem KCoreDirListerCache::findByUrl(const KCoreDirLister *lister, const QUrl &_u) const
758 {
759     QUrl url(_u);
760     url = url.adjusted(QUrl::StripTrailingSlash);
761 
762     const QUrl parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
763     DirItem *dirItem = dirItemForUrl(parentDir);
764     if (dirItem) {
765         // If lister is set, check that it contains this dir
766         if (!lister || lister->d->lstDirs.contains(parentDir)) {
767             // Binary search
768             auto it = std::lower_bound(dirItem->lstItems.begin(), dirItem->lstItems.end(), url);
769             if (it != dirItem->lstItems.end() && it->url() == url) {
770                 return *it;
771             }
772         }
773     }
774 
775     // Maybe _u is a directory itself? (see KDirModelTest::testChmodDirectory)
776     // We check this last, though, we prefer returning a kfileitem with an actual
777     // name if possible (and we make it '.' for root items later).
778     dirItem = dirItemForUrl(url);
779     if (dirItem && !dirItem->rootItem.isNull() && dirItem->rootItem.url() == url) {
780         // If lister is set, check that it contains this dir
781         if (!lister || lister->d->lstDirs.contains(url)) {
782             return dirItem->rootItem;
783         }
784     }
785 
786     return KFileItem();
787 }
788 
slotFilesAdded(const QString & dir)789 void KCoreDirListerCache::slotFilesAdded(const QString &dir /*url*/) // from KDirNotify signals
790 {
791     QUrl urlDir(dir);
792     itemsAddedInDirectory(urlDir);
793 }
794 
itemsAddedInDirectory(const QUrl & urlDir)795 void KCoreDirListerCache::itemsAddedInDirectory(const QUrl &urlDir)
796 {
797     qCDebug(KIO_CORE_DIRLISTER) << urlDir;
798     const QList<QUrl> urls = directoriesForCanonicalPath(urlDir);
799     for (const QUrl &u : urls) {
800         updateDirectory(u);
801     }
802 }
803 
slotFilesRemoved(const QStringList & fileList)804 void KCoreDirListerCache::slotFilesRemoved(const QStringList &fileList) // from KDirNotify signals
805 {
806     // TODO: handling of symlinks-to-directories isn't done here,
807     // because I'm not sure how to do it and keep the performance ok...
808 
809     slotFilesRemoved(QUrl::fromStringList(fileList));
810 }
811 
slotFilesRemoved(const QList<QUrl> & fileList)812 void KCoreDirListerCache::slotFilesRemoved(const QList<QUrl> &fileList)
813 {
814     qCDebug(KIO_CORE_DIRLISTER) << fileList.count();
815     // Group notifications by parent dirs (usually there would be only one parent dir)
816     QMap<QUrl, KFileItemList> removedItemsByDir;
817     QList<QUrl> deletedSubdirs;
818 
819     for (const QUrl &url : fileList) {
820         DirItem *dirItem = dirItemForUrl(url); // is it a listed directory?
821         if (dirItem) {
822             deletedSubdirs.append(url);
823             if (!dirItem->rootItem.isNull()) {
824                 removedItemsByDir[url].append(dirItem->rootItem);
825             }
826         }
827 
828         const QUrl parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
829         dirItem = dirItemForUrl(parentDir);
830         if (!dirItem) {
831             continue;
832         }
833         for (auto fit = dirItem->lstItems.begin(), fend = dirItem->lstItems.end(); fit != fend; ++fit) {
834             if ((*fit).url() == url) {
835                 const KFileItem fileitem = *fit;
836                 removedItemsByDir[parentDir].append(fileitem);
837                 // If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case.
838                 if (fileitem.isNull() || fileitem.isDir()) {
839                     deletedSubdirs.append(url);
840                 }
841                 dirItem->lstItems.erase(fit); // remove fileitem from list
842                 break;
843             }
844         }
845     }
846 
847     for (auto rit = removedItemsByDir.constBegin(), cend = removedItemsByDir.constEnd(); rit != cend; ++rit) {
848         // Tell the views about it before calling deleteDir.
849         // They might need the subdirs' file items (see the dirtree).
850         auto dit = directoryData.constFind(rit.key());
851         if (dit != directoryData.constEnd()) {
852             itemsDeleted((*dit).listersCurrentlyHolding, rit.value());
853         }
854     }
855 
856     for (const QUrl &url : std::as_const(deletedSubdirs)) {
857         // in case of a dir, check if we have any known children, there's much to do in that case
858         // (stopping jobs, removing dirs from cache etc.)
859         deleteDir(url);
860     }
861 }
862 
slotFilesChanged(const QStringList & fileList)863 void KCoreDirListerCache::slotFilesChanged(const QStringList &fileList) // from KDirNotify signals
864 {
865     qCDebug(KIO_CORE_DIRLISTER) << fileList;
866     QList<QUrl> dirsToUpdate;
867     for (const QString &fileUrl : fileList) {
868         const QUrl url(fileUrl);
869         const KFileItem &fileitem = findByUrl(nullptr, url);
870         if (fileitem.isNull()) {
871             qCDebug(KIO_CORE_DIRLISTER) << "item not found for" << url;
872             continue;
873         }
874         if (url.isLocalFile()) {
875             pendingUpdates.insert(url.toLocalFile()); // delegate the work to processPendingUpdates
876         } else {
877             pendingRemoteUpdates.insert(fileitem);
878             // For remote files, we won't be able to figure out the new information,
879             // we have to do a update (directory listing)
880             const QUrl dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
881             if (!dirsToUpdate.contains(dir)) {
882                 dirsToUpdate.prepend(dir);
883             }
884         }
885     }
886 
887     for (const QUrl &dirUrl : std::as_const(dirsToUpdate)) {
888         updateDirectory(dirUrl);
889     }
890     // ## TODO problems with current jobs listing/updating that dir
891     // ( see kde-2.2.2's kdirlister )
892 
893     processPendingUpdates();
894 }
895 
slotFileRenamed(const QString & _src,const QString & _dst,const QString & dstPath)896 void KCoreDirListerCache::slotFileRenamed(const QString &_src, const QString &_dst, const QString &dstPath) // from KDirNotify signals
897 {
898     QUrl src(_src);
899     QUrl dst(_dst);
900     qCDebug(KIO_CORE_DIRLISTER) << src << "->" << dst;
901 #ifdef DEBUG_CACHE
902     printDebug();
903 #endif
904 
905     QUrl oldurl = src.adjusted(QUrl::StripTrailingSlash);
906     KFileItem fileitem = findByUrl(nullptr, oldurl);
907     if (fileitem.isNull()) {
908         qCDebug(KIO_CORE_DIRLISTER) << "Item not found:" << oldurl;
909         return;
910     }
911 
912     const KFileItem oldItem = fileitem;
913 
914     // Dest already exists? Was overwritten then (testcase: #151851)
915     // We better emit it as deleted -before- doing the renaming, otherwise
916     // the "update" mechanism will emit the old one as deleted and
917     // kdirmodel will delete the new (renamed) one!
918     const KFileItem &existingDestItem = findByUrl(nullptr, dst);
919     if (!existingDestItem.isNull()) {
920         qCDebug(KIO_CORE_DIRLISTER) << dst << "already existed, let's delete it";
921         slotFilesRemoved(QList<QUrl>{dst});
922     }
923 
924     // If the item had a UDS_URL as well as UDS_NAME set, the user probably wants
925     // to be updating the name only (since they can't see the URL).
926     // Check to see if a URL exists, and if so, if only the file part has changed,
927     // only update the name and not the underlying URL.
928     bool nameOnly = !fileitem.entry().stringValue(KIO::UDSEntry::UDS_URL).isEmpty();
929     nameOnly = nameOnly && src.adjusted(QUrl::RemoveFilename) == dst.adjusted(QUrl::RemoveFilename);
930 
931     if (!nameOnly && fileitem.isDir()) {
932         renameDir(oldurl, dst);
933         // #172945 - if the fileitem was the root item of a DirItem that was just removed from the cache,
934         // then it's a dangling pointer now...
935         fileitem = findByUrl(nullptr, oldurl);
936         if (fileitem.isNull()) { // deleted from cache altogether, #188807
937             return;
938         }
939     }
940 
941     // Now update the KFileItem representing that file or dir (not exclusive with the above!)
942     if (!oldItem.isLocalFile() && !oldItem.localPath().isEmpty()
943         && dstPath.isEmpty()) { // it uses UDS_LOCAL_PATH and we don't know the new path? needs an update then
944         slotFilesChanged(QStringList{src.toString()});
945     } else {
946         const QUrl &itemOldUrl = fileitem.url();
947         if (nameOnly) {
948             fileitem.setName(dst.fileName());
949         } else {
950             fileitem.setUrl(dst);
951         }
952 
953         if (!dstPath.isEmpty()) {
954             fileitem.setLocalPath(dstPath);
955         }
956 
957         fileitem.refreshMimeType();
958         fileitem.determineMimeType();
959         reinsert(fileitem, itemOldUrl);
960 
961         const std::set<KCoreDirLister *> listers = emitRefreshItem(oldItem, fileitem);
962         for (KCoreDirLister *kdl : listers) {
963             kdl->d->emitItems();
964         }
965     }
966 
967 #ifdef DEBUG_CACHE
968     printDebug();
969 #endif
970 }
971 
emitRefreshItem(const KFileItem & oldItem,const KFileItem & fileitem)972 std::set<KCoreDirLister *> KCoreDirListerCache::emitRefreshItem(const KFileItem &oldItem, const KFileItem &fileitem)
973 {
974     qCDebug(KIO_CORE_DIRLISTER) << "old:" << oldItem.name() << oldItem.url() << "new:" << fileitem.name() << fileitem.url();
975     // Look whether this item was shown in any view, i.e. held by any dirlister
976     const QUrl parentDir = oldItem.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
977     DirectoryDataHash::iterator dit = directoryData.find(parentDir);
978     QList<KCoreDirLister *> listers;
979     // Also look in listersCurrentlyListing, in case the user manages to rename during a listing
980     if (dit != directoryData.end()) {
981         listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing;
982     }
983     if (oldItem.isDir()) {
984         // For a directory, look for dirlisters where it's the root item.
985         dit = directoryData.find(oldItem.url());
986         if (dit != directoryData.end()) {
987             listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing;
988         }
989     }
990     std::set<KCoreDirLister *> listersToRefresh;
991     for (KCoreDirLister *kdl : std::as_const(listers)) {
992         // deduplicate listers
993         listersToRefresh.insert(kdl);
994     }
995     for (KCoreDirLister *kdl : std::as_const(listersToRefresh)) {
996         // For a directory, look for dirlisters where it's the root item.
997         QUrl directoryUrl(oldItem.url());
998         if (oldItem.isDir() && kdl->d->rootFileItem == oldItem) {
999             const KFileItem oldRootItem = kdl->d->rootFileItem;
1000             kdl->d->rootFileItem = fileitem;
1001             kdl->d->addRefreshItem(directoryUrl, oldRootItem, fileitem);
1002         } else {
1003             directoryUrl = directoryUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
1004             kdl->d->addRefreshItem(directoryUrl, oldItem, fileitem);
1005         }
1006     }
1007     return listersToRefresh;
1008 }
1009 
directoriesForCanonicalPath(const QUrl & dir) const1010 QList<QUrl> KCoreDirListerCache::directoriesForCanonicalPath(const QUrl &dir) const
1011 {
1012     QList<QUrl> urlList = canonicalUrls.value(dir);
1013     // make unique
1014     if (urlList.size() > 1) {
1015         std::sort(urlList.begin(), urlList.end());
1016         auto end_unique = std::unique(urlList.begin(), urlList.end());
1017         urlList.erase(end_unique, urlList.end());
1018     }
1019 
1020     QList<QUrl> dirs({dir});
1021     dirs.append(urlList);
1022 
1023     if (dirs.count() > 1) {
1024         qCDebug(KIO_CORE_DIRLISTER) << dir << "known as" << dirs;
1025     }
1026     return dirs;
1027 }
1028 
1029 // private slots
1030 
1031 // Called by KDirWatch - usually when a dir we're watching has been modified,
1032 // but it can also be called for a file.
slotFileDirty(const QString & path)1033 void KCoreDirListerCache::slotFileDirty(const QString &path)
1034 {
1035     qCDebug(KIO_CORE_DIRLISTER) << path;
1036     QUrl url = QUrl::fromLocalFile(path).adjusted(QUrl::StripTrailingSlash);
1037     // File or dir?
1038     bool isDir;
1039     const KFileItem item = itemForUrl(url);
1040 
1041     if (!item.isNull()) {
1042         isDir = item.isDir();
1043     } else {
1044         QFileInfo info(path);
1045         if (!info.exists()) {
1046             return; // error
1047         }
1048         isDir = info.isDir();
1049     }
1050 
1051     if (isDir) {
1052         const QList<QUrl> urls = directoriesForCanonicalPath(url);
1053         for (const QUrl &dir : urls) {
1054             handleFileDirty(dir); // e.g. for permission changes
1055             handleDirDirty(dir);
1056         }
1057     } else {
1058         const QList<QUrl> urls = directoriesForCanonicalPath(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
1059         for (const QUrl &dir : urls) {
1060             QUrl aliasUrl(dir);
1061             aliasUrl.setPath(concatPaths(aliasUrl.path(), url.fileName()));
1062             handleFileDirty(aliasUrl);
1063         }
1064     }
1065 }
1066 
1067 // Called by slotFileDirty
handleDirDirty(const QUrl & url)1068 void KCoreDirListerCache::handleDirDirty(const QUrl &url)
1069 {
1070     // A dir: launch an update job if anyone cares about it
1071 
1072     // This also means we can forget about pending updates to individual files in that dir
1073     const QString dir = url.toLocalFile();
1074     QString dirPath = dir;
1075     if (!dirPath.endsWith(QLatin1Char('/'))) {
1076         dirPath += QLatin1Char('/');
1077     }
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
handleFileDirty(const QUrl & 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     QString filePath = url.toLocalFile();
1105     if (existingItem.isNull()) {
1106         // No - update the parent dir then
1107         handleDirDirty(dir);
1108     }
1109 
1110     // Delay updating the file, FAM is flooding us with events
1111     if (checkUpdate(dir)) {
1112         const auto [it, isInserted] = pendingUpdates.insert(filePath);
1113         if (isInserted && !pendingUpdateTimer.isActive()) {
1114             pendingUpdateTimer.start(200);
1115         }
1116     }
1117 }
1118 
slotFileCreated(const QString & path)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 
slotFileDeleted(const QString & path)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(concatPaths(urlInfo.path(), fileName));
1138         fileUrls << urlInfo.toString();
1139     }
1140     slotFilesRemoved(fileUrls);
1141 }
1142 
slotEntries(KIO::Job * job,const KIO::UDSEntryList & entries)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     for (const auto &entry : entries) {
1183         const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
1184 
1185         Q_ASSERT(!name.isEmpty());
1186         if (name.isEmpty()) {
1187             continue;
1188         }
1189 
1190         if (name == QLatin1Char('.')) {
1191             Q_ASSERT(dir->rootItem.isNull());
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             // Add the items sorted by url, needed by findByUrl
1229             dir->insert(item);
1230 
1231             for (KCoreDirLister *kdl : listers) {
1232                 kdl->d->addNewItem(url, item);
1233             }
1234         }
1235     }
1236 
1237     for (KCoreDirLister *kdl : listers) {
1238         kdl->d->emitItems();
1239     }
1240 }
1241 
slotResult(KJob * j)1242 void KCoreDirListerCache::slotResult(KJob *j)
1243 {
1244 #ifdef DEBUG_CACHE
1245     // printDebug();
1246 #endif
1247 
1248     Q_ASSERT(j);
1249     KIO::ListJob *job = static_cast<KIO::ListJob *>(j);
1250     runningListJobs.remove(job);
1251 
1252     QUrl jobUrl(joburl(job));
1253     jobUrl = jobUrl.adjusted(QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections
1254 
1255     qCDebug(KIO_CORE_DIRLISTER) << "finished listing" << jobUrl;
1256 
1257     const auto dit = directoryData.find(jobUrl);
1258     if (dit == directoryData.end()) {
1259         qCWarning(KIO_CORE) << "Nothing found in directoryData for URL" << jobUrl;
1260 #ifndef NDEBUG
1261         printDebug();
1262 #endif
1263         Q_ASSERT(dit != directoryData.end());
1264         return;
1265     }
1266     KCoreDirListerCacheDirectoryData &dirData = *dit;
1267     if (dirData.listersCurrentlyListing.isEmpty()) {
1268         qCWarning(KIO_CORE) << "OOOOPS, nothing in directoryData.listersCurrentlyListing for" << jobUrl;
1269         // We're about to assert; dump the current state...
1270 #ifndef NDEBUG
1271         printDebug();
1272 #endif
1273         Q_ASSERT(!dirData.listersCurrentlyListing.isEmpty());
1274     }
1275     const QList<KCoreDirLister *> listers = dirData.listersCurrentlyListing;
1276 
1277     // move all listers to the holding list, do it before emitting
1278     // the signals to make sure it exists in KCoreDirListerCache in case someone
1279     // calls listDir during the signal emission
1280     Q_ASSERT(dirData.listersCurrentlyHolding.isEmpty());
1281     dirData.moveListersWithoutCachedItemsJob(jobUrl);
1282 
1283     if (job->error()) {
1284         bool errorShown = false;
1285         for (KCoreDirLister *kdl : listers) {
1286             kdl->d->jobDone(job);
1287             if (job->error() != KJob::KilledJobError) {
1288                 Q_EMIT kdl->jobError(job);
1289                 if (kdl->d->m_autoErrorHandling && !errorShown) {
1290                     errorShown = true; // do it only once
1291                     if (job->uiDelegate()) {
1292                         job->uiDelegate()->showErrorMessage();
1293                     }
1294                 }
1295 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 82)
1296                 kdl->handleError(job);
1297 #endif
1298             }
1299             const bool silent = job->property("_kdlc_silent").toBool();
1300             if (!silent) {
1301 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1302                 Q_EMIT kdl->canceled(jobUrl);
1303 #endif
1304                 Q_EMIT kdl->listingDirCanceled(jobUrl);
1305             }
1306 
1307             if (kdl->d->numJobs() == 0) {
1308                 kdl->d->complete = true;
1309                 if (!silent) {
1310                     Q_EMIT kdl->canceled();
1311                 }
1312             }
1313         }
1314     } else {
1315         DirItem *dir = itemsInUse.value(jobUrl);
1316         Q_ASSERT(dir);
1317         dir->complete = true;
1318 
1319         for (KCoreDirLister *kdl : listers) {
1320             kdl->d->jobDone(job);
1321 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1322             Q_EMIT kdl->completed(jobUrl);
1323 #endif
1324             Q_EMIT kdl->listingDirCompleted(jobUrl);
1325             if (kdl->d->numJobs() == 0) {
1326                 kdl->d->complete = true;
1327                 Q_EMIT kdl->completed();
1328             }
1329         }
1330     }
1331 
1332     // TODO: hmm, if there was an error and job is a parent of one or more
1333     // of the pending urls we should cancel it/them as well
1334     processPendingUpdates();
1335 
1336     if (job->property("need_another_update").toBool()) {
1337         updateDirectory(jobUrl);
1338     }
1339 
1340 #ifdef DEBUG_CACHE
1341     printDebug();
1342 #endif
1343 }
1344 
slotRedirection(KIO::Job * j,const QUrl & url)1345 void KCoreDirListerCache::slotRedirection(KIO::Job *j, const QUrl &url)
1346 {
1347     Q_ASSERT(j);
1348     KIO::ListJob *job = static_cast<KIO::ListJob *>(j);
1349 
1350     QUrl oldUrl(job->url()); // here we really need the old url!
1351     QUrl newUrl(url);
1352 
1353     // strip trailing slashes
1354     oldUrl = oldUrl.adjusted(QUrl::StripTrailingSlash);
1355     newUrl = newUrl.adjusted(QUrl::StripTrailingSlash);
1356 
1357     if (oldUrl == newUrl) {
1358         qCDebug(KIO_CORE_DIRLISTER) << "New redirection url same as old, giving up.";
1359         return;
1360     } else if (newUrl.isEmpty()) {
1361         qCDebug(KIO_CORE_DIRLISTER) << "New redirection url is empty, giving up.";
1362         return;
1363     }
1364 
1365     qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl;
1366 
1367 #ifdef DEBUG_CACHE
1368     // Can't do that here. KCoreDirListerCache::joburl() will use the new url already,
1369     // while our data structures haven't been updated yet -> assert fail.
1370     // printDebug();
1371 #endif
1372 
1373     // I don't think there can be dirItems that are children of oldUrl.
1374     // Am I wrong here? And even if so, we don't need to delete them, right?
1375     // DF: redirection happens before listDir emits any item. Makes little sense otherwise.
1376 
1377     // oldUrl cannot be in itemsCached because only completed items are moved there
1378     DirItem *dir = itemsInUse.take(oldUrl);
1379     Q_ASSERT(dir);
1380 
1381     DirectoryDataHash::iterator dit = directoryData.find(oldUrl);
1382     Q_ASSERT(dit != directoryData.end());
1383     KCoreDirListerCacheDirectoryData oldDirData = *dit;
1384     directoryData.erase(dit);
1385     Q_ASSERT(!oldDirData.listersCurrentlyListing.isEmpty());
1386     const QList<KCoreDirLister *> listers = oldDirData.listersCurrentlyListing;
1387     Q_ASSERT(!listers.isEmpty());
1388 
1389     for (KCoreDirLister *kdl : listers) {
1390         kdl->d->redirect(oldUrl, newUrl, false /*clear items*/);
1391     }
1392 
1393     // when a lister was stopped before the job emits the redirection signal, the old url will
1394     // also be in listersCurrentlyHolding
1395     const QList<KCoreDirLister *> holders = oldDirData.listersCurrentlyHolding;
1396     for (KCoreDirLister *kdl : holders) {
1397         kdl->jobStarted(job);
1398         // do it like when starting a new list-job that will redirect later
1399         // TODO: maybe don't emit started if there's an update running for newUrl already?
1400         Q_EMIT kdl->started(oldUrl);
1401 
1402         kdl->d->redirect(oldUrl, newUrl, false /*clear items*/);
1403     }
1404 
1405     const QList<KCoreDirLister *> allListers = listers + holders;
1406 
1407     DirItem *newDir = itemsInUse.value(newUrl);
1408     if (newDir) {
1409         qCDebug(KIO_CORE_DIRLISTER) << newUrl << "already in use";
1410 
1411         // only in this case there can newUrl already be in listersCurrentlyListing or listersCurrentlyHolding
1412         delete dir;
1413 
1414         // get the job if one's running for newUrl already (can be a list-job or an update-job), but
1415         // do not return this 'job', which would happen because of the use of redirectionURL()
1416         KIO::ListJob *oldJob = jobForUrl(newUrl, job);
1417 
1418         // listers of newUrl with oldJob: forget about the oldJob and use the already running one
1419         // which will be converted to an updateJob
1420         KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl];
1421 
1422         QList<KCoreDirLister *> &curListers = newDirData.listersCurrentlyListing;
1423         if (!curListers.isEmpty()) {
1424             qCDebug(KIO_CORE_DIRLISTER) << "and it is currently listed";
1425 
1426             Q_ASSERT(oldJob); // ?!
1427 
1428             for (KCoreDirLister *kdl : std::as_const(curListers)) { // listers of newUrl
1429                 kdl->d->jobDone(oldJob);
1430 
1431                 kdl->jobStarted(job);
1432                 kdl->d->connectJob(job);
1433             }
1434 
1435             // append listers of oldUrl with newJob to listers of newUrl with oldJob
1436             for (KCoreDirLister *kdl : listers) {
1437                 curListers.append(kdl);
1438             }
1439         } else {
1440             curListers = listers;
1441         }
1442 
1443         if (oldJob) { // kill the old job, be it a list-job or an update-job
1444             killJob(oldJob);
1445         }
1446 
1447         // holders of newUrl: use the already running job which will be converted to an updateJob
1448         QList<KCoreDirLister *> &curHolders = newDirData.listersCurrentlyHolding;
1449         if (!curHolders.isEmpty()) {
1450             qCDebug(KIO_CORE_DIRLISTER) << "and it is currently held.";
1451 
1452             for (KCoreDirLister *kdl : std::as_const(curHolders)) { // holders of newUrl
1453                 kdl->jobStarted(job);
1454                 Q_EMIT kdl->started(newUrl);
1455             }
1456 
1457             // append holders of oldUrl to holders of newUrl
1458             for (KCoreDirLister *kdl : holders) {
1459                 curHolders.append(kdl);
1460             }
1461         } else {
1462             curHolders = holders;
1463         }
1464 
1465         // emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed
1466         // TODO: make this a separate method?
1467         for (KCoreDirLister *kdl : allListers) {
1468             if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) {
1469                 kdl->d->rootFileItem = newDir->rootItem;
1470             }
1471 
1472             kdl->d->addNewItems(newUrl, newDir->lstItems);
1473             kdl->d->emitItems();
1474         }
1475     } else if ((newDir = itemsCached.take(newUrl))) {
1476         qCDebug(KIO_CORE_DIRLISTER) << newUrl << "is unused, but already in the cache.";
1477 
1478         delete dir;
1479         itemsInUse.insert(newUrl, newDir);
1480         KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl];
1481         newDirData.listersCurrentlyListing = listers;
1482         newDirData.listersCurrentlyHolding = holders;
1483 
1484         // emit old items: listers, holders
1485         for (KCoreDirLister *kdl : allListers) {
1486             if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) {
1487                 kdl->d->rootFileItem = newDir->rootItem;
1488             }
1489 
1490             kdl->d->addNewItems(newUrl, newDir->lstItems);
1491             kdl->d->emitItems();
1492         }
1493     } else {
1494         qCDebug(KIO_CORE_DIRLISTER) << newUrl << "has not been listed yet.";
1495 
1496         dir->rootItem = KFileItem();
1497         dir->lstItems.clear();
1498         dir->redirect(newUrl);
1499         itemsInUse.insert(newUrl, dir);
1500         KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl];
1501         newDirData.listersCurrentlyListing = listers;
1502         newDirData.listersCurrentlyHolding = holders;
1503 
1504         if (holders.isEmpty()) {
1505 #ifdef DEBUG_CACHE
1506             printDebug();
1507 #endif
1508             return; // only in this case the job doesn't need to be converted,
1509         }
1510     }
1511 
1512     // make the job an update job
1513     job->disconnect(this);
1514 
1515     connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotUpdateEntries);
1516     connect(job, &KJob::result, this, &KCoreDirListerCache::slotUpdateResult);
1517 
1518     // FIXME: autoUpdate-Counts!!
1519 
1520 #ifdef DEBUG_CACHE
1521     printDebug();
1522 #endif
1523 }
1524 
1525 struct KCoreDirListerCache::ItemInUseChange {
ItemInUseChangeKCoreDirListerCache::ItemInUseChange1526     ItemInUseChange(const QUrl &old, const QUrl &newU, DirItem *di)
1527         : oldUrl(old)
1528         , newUrl(newU)
1529         , dirItem(di)
1530     {
1531     }
1532     QUrl oldUrl;
1533     QUrl newUrl;
1534     DirItem *dirItem;
1535 };
1536 
renameDir(const QUrl & oldUrl,const QUrl & newUrl)1537 void KCoreDirListerCache::renameDir(const QUrl &oldUrl, const QUrl &newUrl)
1538 {
1539     qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl;
1540 
1541     std::vector<ItemInUseChange> itemsToChange;
1542     std::set<KCoreDirLister *> listers;
1543 
1544     // Look at all dirs being listed/shown
1545     for (auto itu = itemsInUse.begin(), ituend = itemsInUse.end(); itu != ituend; ++itu) {
1546         DirItem *dir = itu.value();
1547         const QUrl &oldDirUrl = itu.key();
1548         qCDebug(KIO_CORE_DIRLISTER) << "itemInUse:" << oldDirUrl;
1549         // Check if this dir is oldUrl, or a subfolder of it
1550         if (oldDirUrl == oldUrl || oldUrl.isParentOf(oldDirUrl)) {
1551             // TODO should use KUrl::cleanpath like isParentOf does
1552             QString relPath = oldDirUrl.path().mid(oldUrl.path().length() + 1);
1553 
1554             QUrl newDirUrl(newUrl); // take new base
1555             if (!relPath.isEmpty()) {
1556                 newDirUrl.setPath(concatPaths(newDirUrl.path(), relPath)); // add unchanged relative path
1557             }
1558             qCDebug(KIO_CORE_DIRLISTER) << "new url=" << newDirUrl;
1559 
1560             // Update URL in dir item and in itemsInUse
1561             dir->redirect(newDirUrl);
1562 
1563             itemsToChange.emplace_back(oldDirUrl.adjusted(QUrl::StripTrailingSlash), newDirUrl.adjusted(QUrl::StripTrailingSlash), dir);
1564             // Rename all items under that dir
1565             // If all items of the directory change the same part of their url, the order is not
1566             // changed, therefore just change it in the list.
1567             for (KFileItem &item : dir->lstItems) {
1568                 const KFileItem oldItem = item;
1569                 KFileItem newItem = oldItem;
1570                 const QUrl &oldItemUrl = oldItem.url();
1571                 QUrl newItemUrl(oldItemUrl);
1572                 newItemUrl.setPath(concatPaths(newDirUrl.path(), oldItemUrl.fileName()));
1573                 qCDebug(KIO_CORE_DIRLISTER) << "renaming" << oldItemUrl << "to" << newItemUrl;
1574                 newItem.setUrl(newItemUrl);
1575 
1576                 listers.merge(emitRefreshItem(oldItem, newItem));
1577                 // Change the item
1578                 item.setUrl(newItemUrl);
1579             }
1580         }
1581     }
1582 
1583     for (KCoreDirLister *kdl : listers) {
1584         kdl->d->emitItems();
1585     }
1586 
1587     // Do the changes to itemsInUse out of the loop to avoid messing up iterators,
1588     // and so that emitRefreshItem can find the stuff in the hash.
1589     for (const ItemInUseChange &i : itemsToChange) {
1590         itemsInUse.remove(i.oldUrl);
1591         itemsInUse.insert(i.newUrl, i.dirItem);
1592     }
1593     // Now that all the caches are updated and consistent, emit the redirection.
1594     for (const ItemInUseChange &i : itemsToChange) {
1595         emitRedirections(QUrl(i.oldUrl), QUrl(i.newUrl));
1596     }
1597     // Is oldUrl a directory in the cache?
1598     // Remove any child of oldUrl from the cache - even if the renamed dir itself isn't in it!
1599     removeDirFromCache(oldUrl);
1600     // TODO rename, instead.
1601 }
1602 
1603 // helper for renameDir, not used for redirections from KIO::listDir().
emitRedirections(const QUrl & _oldUrl,const QUrl & _newUrl)1604 void KCoreDirListerCache::emitRedirections(const QUrl &_oldUrl, const QUrl &_newUrl)
1605 {
1606     qCDebug(KIO_CORE_DIRLISTER) << _oldUrl << "->" << _newUrl;
1607     const QUrl oldUrl = _oldUrl.adjusted(QUrl::StripTrailingSlash);
1608     const QUrl newUrl = _newUrl.adjusted(QUrl::StripTrailingSlash);
1609 
1610     KIO::ListJob *job = jobForUrl(oldUrl);
1611     if (job) {
1612         killJob(job);
1613     }
1614 
1615     // Check if we were listing this dir. Need to abort and restart with new name in that case.
1616     DirectoryDataHash::iterator dit = directoryData.find(oldUrl);
1617     if (dit == directoryData.end()) {
1618         return;
1619     }
1620     const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing;
1621     const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding;
1622 
1623     KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl];
1624 
1625     // Tell the world that the job listing the old url is dead.
1626     for (KCoreDirLister *kdl : listers) {
1627         if (job) {
1628             kdl->d->jobDone(job);
1629         }
1630 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1631         Q_EMIT kdl->canceled(oldUrl);
1632 #endif
1633         Q_EMIT kdl->listingDirCanceled(oldUrl);
1634     }
1635     newDirData.listersCurrentlyListing += listers;
1636 
1637     // Check if we are currently displaying this directory (odds opposite wrt above)
1638     for (KCoreDirLister *kdl : holders) {
1639         if (job) {
1640             kdl->d->jobDone(job);
1641         }
1642     }
1643     newDirData.listersCurrentlyHolding += holders;
1644     directoryData.erase(dit);
1645 
1646     if (!listers.isEmpty()) {
1647         updateDirectory(newUrl);
1648 
1649         // Tell the world about the new url
1650         for (KCoreDirLister *kdl : listers) {
1651             Q_EMIT kdl->started(newUrl);
1652         }
1653     }
1654 
1655     // And notify the dirlisters of the redirection
1656     for (KCoreDirLister *kdl : holders) {
1657         kdl->d->redirect(oldUrl, newUrl, true /*keep items*/);
1658     }
1659 }
1660 
removeDirFromCache(const QUrl & dir)1661 void KCoreDirListerCache::removeDirFromCache(const QUrl &dir)
1662 {
1663     qCDebug(KIO_CORE_DIRLISTER) << dir;
1664     const QList<QUrl> cachedDirs = itemsCached.keys(); // seems slow, but there's no qcache iterator...
1665     for (const QUrl &cachedDir : cachedDirs) {
1666         if (dir == cachedDir || dir.isParentOf(cachedDir)) {
1667             itemsCached.remove(cachedDir);
1668         }
1669     }
1670 }
1671 
slotUpdateEntries(KIO::Job * job,const KIO::UDSEntryList & list)1672 void KCoreDirListerCache::slotUpdateEntries(KIO::Job *job, const KIO::UDSEntryList &list)
1673 {
1674     runningListJobs[static_cast<KIO::ListJob *>(job)] += list;
1675 }
1676 
slotUpdateResult(KJob * j)1677 void KCoreDirListerCache::slotUpdateResult(KJob *j)
1678 {
1679     Q_ASSERT(j);
1680     KIO::ListJob *job = static_cast<KIO::ListJob *>(j);
1681 
1682     QUrl jobUrl(joburl(job));
1683     jobUrl = jobUrl.adjusted(QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections
1684 
1685     qCDebug(KIO_CORE_DIRLISTER) << "finished update" << jobUrl;
1686 
1687     KCoreDirListerCacheDirectoryData &dirData = directoryData[jobUrl];
1688     // Collect the dirlisters which were listing the URL using that ListJob
1689     // plus those that were already holding that URL - they all get updated.
1690     dirData.moveListersWithoutCachedItemsJob(jobUrl);
1691     const QList<KCoreDirLister *> listers = dirData.listersCurrentlyHolding + dirData.listersCurrentlyListing;
1692 
1693     // once we are updating dirs that are only in the cache this will fail!
1694     Q_ASSERT(!listers.isEmpty());
1695 
1696     if (job->error()) {
1697         for (KCoreDirLister *kdl : listers) {
1698             kdl->d->jobDone(job);
1699 
1700             // don't bother the user: no jobError signal emitted
1701 
1702             const bool silent = job->property("_kdlc_silent").toBool();
1703             if (!silent) {
1704 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1705                 Q_EMIT kdl->canceled(jobUrl);
1706 #endif
1707                 Q_EMIT kdl->listingDirCanceled(jobUrl);
1708             }
1709             if (kdl->d->numJobs() == 0) {
1710                 kdl->d->complete = true;
1711                 if (!silent) {
1712                     Q_EMIT kdl->canceled();
1713                 }
1714             }
1715         }
1716 
1717         runningListJobs.remove(job);
1718 
1719         // TODO: if job is a parent of one or more
1720         // of the pending urls we should cancel them
1721         processPendingUpdates();
1722         return;
1723     }
1724 
1725     DirItem *dir = itemsInUse.value(jobUrl, nullptr);
1726     if (!dir) {
1727         qCWarning(KIO_CORE) << "Internal error: itemsInUse did not contain" << jobUrl;
1728 #ifndef NDEBUG
1729         printDebug();
1730 #endif
1731         Q_ASSERT(dir);
1732     } else {
1733         dir->complete = true;
1734     }
1735 
1736     // check if anyone wants the MIME types immediately
1737     bool delayedMimeTypes = true;
1738     for (const KCoreDirLister *kdl : listers) {
1739         delayedMimeTypes &= kdl->d->delayedMimeTypes;
1740     }
1741 
1742     typedef QHash<QString, KFileItem> FileItemHash; // fileName -> KFileItem
1743     FileItemHash fileItems;
1744 
1745     // Fill the hash from the old list of items. We'll remove entries as we see them
1746     // in the new listing, and the resulting hash entries will be the deleted items.
1747     for (const KFileItem &item : std::as_const(dir->lstItems)) {
1748         fileItems.insert(item.name(), item);
1749     }
1750 
1751     CacheHiddenFile *cachedHidden = nullptr;
1752     bool dotHiddenChecked = false;
1753     const KIO::UDSEntryList &buf = runningListJobs.value(job);
1754     for (const auto &entry : buf) {
1755         // Form the complete url
1756         KFileItem item(entry, jobUrl, delayedMimeTypes, true);
1757 
1758         const QString name = item.name();
1759         Q_ASSERT(!name.isEmpty()); // A kioslave setting an empty UDS_NAME is utterly broken, fix the kioslave!
1760 
1761         // we duplicate the check for dotdot here, to avoid iterating over
1762         // all items again and checking in matchesFilter() that way.
1763         if (name.isEmpty() || name == QLatin1String("..")) {
1764             continue;
1765         }
1766 
1767         if (name == QLatin1Char('.')) {
1768             // if the update was started before finishing the original listing
1769             // there is no root item yet
1770             if (dir->rootItem.isNull()) {
1771                 dir->rootItem = item;
1772 
1773                 for (KCoreDirLister *kdl : listers) {
1774                     if (kdl->d->rootFileItem.isNull() && kdl->d->url == jobUrl) {
1775                         kdl->d->rootFileItem = dir->rootItem;
1776                     }
1777                 }
1778             }
1779             continue;
1780         } else {
1781             // get the names of the files listed in ".hidden", if it exists and is a local file
1782             if (!dotHiddenChecked) {
1783                 const QString localPath = item.localPath();
1784                 if (!localPath.isEmpty()) {
1785                     const QString rootItemPath = QFileInfo(localPath).absolutePath();
1786                     cachedHidden = cachedDotHiddenForDir(rootItemPath);
1787                 }
1788                 dotHiddenChecked = true;
1789             }
1790         }
1791 
1792         // hide file if listed in ".hidden"
1793         if (cachedHidden && cachedHidden->listedFiles.find(name) != cachedHidden->listedFiles.cend()) {
1794             item.setHidden();
1795         }
1796 
1797         // Find this item
1798         FileItemHash::iterator fiit = fileItems.find(item.name());
1799         if (fiit != fileItems.end()) {
1800             const KFileItem tmp = fiit.value();
1801             auto pru_it = pendingRemoteUpdates.find(tmp);
1802             const bool inPendingRemoteUpdates = pru_it != pendingRemoteUpdates.end();
1803 
1804             // check if something changed for this file, using KFileItem::cmp()
1805             if (!tmp.cmp(item) || inPendingRemoteUpdates) {
1806                 if (inPendingRemoteUpdates) {
1807                     pendingRemoteUpdates.erase(pru_it);
1808                 }
1809 
1810                 qCDebug(KIO_CORE_DIRLISTER) << "file changed:" << tmp.name();
1811 
1812                 reinsert(item, tmp.url());
1813                 for (KCoreDirLister *kdl : listers) {
1814                     kdl->d->addRefreshItem(jobUrl, tmp, item);
1815                 }
1816             }
1817             // Seen, remove
1818             fileItems.erase(fiit);
1819         } else { // this is a new file
1820             qCDebug(KIO_CORE_DIRLISTER) << "new file:" << name;
1821             dir->insert(item);
1822 
1823             for (KCoreDirLister *kdl : listers) {
1824                 kdl->d->addNewItem(jobUrl, item);
1825             }
1826         }
1827     }
1828 
1829     runningListJobs.remove(job);
1830 
1831     if (!fileItems.isEmpty()) {
1832         deleteUnmarkedItems(listers, dir->lstItems, fileItems);
1833     }
1834 
1835     for (KCoreDirLister *kdl : listers) {
1836         kdl->d->emitItems();
1837 
1838         kdl->d->jobDone(job);
1839 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1840         Q_EMIT kdl->completed(jobUrl);
1841 #endif
1842         Q_EMIT kdl->listingDirCompleted(jobUrl);
1843         if (kdl->d->numJobs() == 0) {
1844             kdl->d->complete = true;
1845             Q_EMIT kdl->completed();
1846         }
1847     }
1848 
1849     // TODO: hmm, if there was an error and job is a parent of one or more
1850     // of the pending urls we should cancel it/them as well
1851     processPendingUpdates();
1852 
1853     if (job->property("need_another_update").toBool()) {
1854         updateDirectory(jobUrl);
1855     }
1856 }
1857 
1858 // private
1859 
jobForUrl(const QUrl & url,KIO::ListJob * not_job)1860 KIO::ListJob *KCoreDirListerCache::jobForUrl(const QUrl &url, KIO::ListJob *not_job)
1861 {
1862     auto it = runningListJobs.constBegin();
1863     while (it != runningListJobs.constEnd()) {
1864         KIO::ListJob *job = it.key();
1865         const QUrl jobUrl = joburl(job).adjusted(QUrl::StripTrailingSlash);
1866 
1867         if (jobUrl == url && job != not_job) {
1868             return job;
1869         }
1870         ++it;
1871     }
1872     return nullptr;
1873 }
1874 
joburl(KIO::ListJob * job)1875 const QUrl &KCoreDirListerCache::joburl(KIO::ListJob *job)
1876 {
1877     if (job->redirectionUrl().isValid()) {
1878         return job->redirectionUrl();
1879     } else {
1880         return job->url();
1881     }
1882 }
1883 
killJob(KIO::ListJob * job)1884 void KCoreDirListerCache::killJob(KIO::ListJob *job)
1885 {
1886     runningListJobs.remove(job);
1887     job->disconnect(this);
1888     job->kill();
1889 }
1890 
deleteUnmarkedItems(const QList<KCoreDirLister * > & listers,QList<KFileItem> & lstItems,const QHash<QString,KFileItem> & itemsToDelete)1891 void KCoreDirListerCache::deleteUnmarkedItems(const QList<KCoreDirLister *> &listers,
1892                                               QList<KFileItem> &lstItems,
1893                                               const QHash<QString, KFileItem> &itemsToDelete)
1894 {
1895     // Make list of deleted items (for emitting)
1896     KFileItemList deletedItems;
1897     QHashIterator<QString, KFileItem> kit(itemsToDelete);
1898     while (kit.hasNext()) {
1899         const KFileItem item = kit.next().value();
1900         deletedItems.append(item);
1901         qCDebug(KIO_CORE_DIRLISTER) << "deleted:" << item.name() << item;
1902     }
1903 
1904     // Delete all remaining items
1905     QMutableListIterator<KFileItem> it(lstItems);
1906     while (it.hasNext()) {
1907         if (itemsToDelete.contains(it.next().name())) {
1908             it.remove();
1909         }
1910     }
1911     itemsDeleted(listers, deletedItems);
1912 }
1913 
itemsDeleted(const QList<KCoreDirLister * > & listers,const KFileItemList & deletedItems)1914 void KCoreDirListerCache::itemsDeleted(const QList<KCoreDirLister *> &listers, const KFileItemList &deletedItems)
1915 {
1916     for (KCoreDirLister *kdl : listers) {
1917         kdl->d->emitItemsDeleted(deletedItems);
1918     }
1919 
1920     for (const KFileItem &item : deletedItems) {
1921         if (item.isDir()) {
1922             deleteDir(item.url());
1923         }
1924     }
1925 }
1926 
deleteDir(const QUrl & _dirUrl)1927 void KCoreDirListerCache::deleteDir(const QUrl &_dirUrl)
1928 {
1929     qCDebug(KIO_CORE_DIRLISTER) << _dirUrl;
1930     // unregister and remove the children of the deleted item.
1931     // Idea: tell all the KCoreDirListers that they should forget the dir
1932     //       and then remove it from the cache.
1933 
1934     QUrl dirUrl(_dirUrl.adjusted(QUrl::StripTrailingSlash));
1935 
1936     // Separate itemsInUse iteration and calls to forgetDirs (which modify itemsInUse)
1937     QList<QUrl> affectedItems;
1938 
1939     auto itu = itemsInUse.begin();
1940     const auto ituend = itemsInUse.end();
1941     for (; itu != ituend; ++itu) {
1942         const QUrl &deletedUrl = itu.key();
1943         if (dirUrl == deletedUrl || dirUrl.isParentOf(deletedUrl)) {
1944             affectedItems.append(deletedUrl);
1945         }
1946     }
1947 
1948     for (const QUrl &deletedUrl : std::as_const(affectedItems)) {
1949         // stop all jobs for deletedUrlStr
1950         DirectoryDataHash::iterator dit = directoryData.find(deletedUrl);
1951         if (dit != directoryData.end()) {
1952             // we need a copy because stop modifies the list
1953             const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing;
1954             for (KCoreDirLister *kdl : listers) {
1955                 stopListingUrl(kdl, deletedUrl);
1956             }
1957             // tell listers holding deletedUrl to forget about it
1958             // this will stop running updates for deletedUrl as well
1959 
1960             // we need a copy because forgetDirs modifies the list
1961             const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding;
1962             for (KCoreDirLister *kdl : holders) {
1963                 // lister's root is the deleted item
1964                 if (kdl->d->url == deletedUrl) {
1965                     // tell the view first. It might need the subdirs' items (which forgetDirs will delete)
1966                     if (!kdl->d->rootFileItem.isNull()) {
1967                         Q_EMIT kdl->itemsDeleted(KFileItemList{kdl->d->rootFileItem});
1968                     }
1969                     forgetDirs(kdl);
1970                     kdl->d->rootFileItem = KFileItem();
1971                 } else {
1972                     const bool treeview = kdl->d->lstDirs.count() > 1;
1973                     if (!treeview) {
1974                         Q_EMIT kdl->clear();
1975                         kdl->d->lstDirs.clear();
1976                     } else {
1977                         kdl->d->lstDirs.removeAll(deletedUrl);
1978                     }
1979 
1980                     forgetDirs(kdl, deletedUrl, treeview);
1981                 }
1982             }
1983         }
1984 
1985         // delete the entry for deletedUrl - should not be needed, it's in
1986         // items cached now
1987         int count = itemsInUse.remove(deletedUrl);
1988         Q_ASSERT(count == 0);
1989         Q_UNUSED(count); // keep gcc "unused variable" complaining quiet when in release mode
1990     }
1991 
1992     // remove the children from the cache
1993     removeDirFromCache(dirUrl);
1994 }
1995 
1996 // delayed updating of files, FAM is flooding us with events
processPendingUpdates()1997 void KCoreDirListerCache::processPendingUpdates()
1998 {
1999     std::set<KCoreDirLister *> listers;
2000     for (const QString &file : pendingUpdates) { // always a local path
2001         qCDebug(KIO_CORE_DIRLISTER) << file;
2002         QUrl u = QUrl::fromLocalFile(file);
2003         KFileItem item = findByUrl(nullptr, u); // search all items
2004         if (!item.isNull()) {
2005             // we need to refresh the item, because e.g. the permissions can have changed.
2006             KFileItem oldItem = item;
2007             item.refresh();
2008 
2009             if (!oldItem.cmp(item)) {
2010                 reinsert(item, oldItem.url());
2011                 listers.merge(emitRefreshItem(oldItem, item));
2012             }
2013         }
2014     }
2015     pendingUpdates.clear();
2016     for (KCoreDirLister *kdl : listers) {
2017         kdl->d->emitItems();
2018     }
2019 
2020     // Directories in need of updating
2021     for (const QString &dir : pendingDirectoryUpdates) {
2022         updateDirectory(QUrl::fromLocalFile(dir));
2023     }
2024     pendingDirectoryUpdates.clear();
2025 }
2026 
2027 #ifndef NDEBUG
printDebug()2028 void KCoreDirListerCache::printDebug()
2029 {
2030     qCDebug(KIO_CORE_DIRLISTER) << "Items in use:";
2031     auto itu = itemsInUse.constBegin();
2032     const auto ituend = itemsInUse.constEnd();
2033     for (; itu != ituend; ++itu) {
2034         qCDebug(KIO_CORE_DIRLISTER) << "   " << itu.key() << "URL:" << itu.value()->url
2035                                     << "rootItem:" << (!itu.value()->rootItem.isNull() ? itu.value()->rootItem.url() : QUrl())
2036                                     << "autoUpdates refcount:" << itu.value()->autoUpdates << "complete:" << itu.value()->complete
2037                                     << QStringLiteral("with %1 items.").arg(itu.value()->lstItems.count());
2038     }
2039 
2040     QList<KCoreDirLister *> listersWithoutJob;
2041     qCDebug(KIO_CORE_DIRLISTER) << "Directory data:";
2042     auto dit = directoryData.constBegin();
2043     for (; dit != directoryData.constEnd(); ++dit) {
2044         QString list;
2045         const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing;
2046         for (KCoreDirLister *listit : listers) {
2047             list += QLatin1String(" 0x") + QString::number(reinterpret_cast<qlonglong>(listit), 16);
2048         }
2049         qCDebug(KIO_CORE_DIRLISTER) << "  " << dit.key() << listers.count() << "listers:" << list;
2050         for (KCoreDirLister *listit : listers) {
2051             if (!listit->d->m_cachedItemsJobs.isEmpty()) {
2052                 qCDebug(KIO_CORE_DIRLISTER) << "  Lister" << listit << "has CachedItemsJobs" << listit->d->m_cachedItemsJobs;
2053             } else if (KIO::ListJob *listJob = jobForUrl(dit.key())) {
2054                 qCDebug(KIO_CORE_DIRLISTER) << "  Lister" << listit << "has ListJob" << listJob;
2055             } else {
2056                 listersWithoutJob.append(listit);
2057             }
2058         }
2059 
2060         list.clear();
2061         const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding;
2062         for (KCoreDirLister *listit : holders) {
2063             list += QLatin1String(" 0x") + QString::number(reinterpret_cast<qlonglong>(listit), 16);
2064         }
2065         qCDebug(KIO_CORE_DIRLISTER) << "  " << dit.key() << holders.count() << "holders:" << list;
2066     }
2067 
2068     QMap<KIO::ListJob *, KIO::UDSEntryList>::Iterator jit = runningListJobs.begin();
2069     qCDebug(KIO_CORE_DIRLISTER) << "Jobs:";
2070     for (; jit != runningListJobs.end(); ++jit) {
2071         qCDebug(KIO_CORE_DIRLISTER) << "   " << jit.key() << "listing" << joburl(jit.key()) << ":" << (*jit).count() << "entries.";
2072     }
2073 
2074     qCDebug(KIO_CORE_DIRLISTER) << "Items in cache:";
2075     const QList<QUrl> cachedDirs = itemsCached.keys();
2076     for (const QUrl &cachedDir : cachedDirs) {
2077         DirItem *dirItem = itemsCached.object(cachedDir);
2078         qCDebug(KIO_CORE_DIRLISTER) << "   " << cachedDir
2079                                     << "rootItem:" << (!dirItem->rootItem.isNull() ? dirItem->rootItem.url().toString() : QStringLiteral("NULL")) << "with"
2080                                     << dirItem->lstItems.count() << "items.";
2081     }
2082 
2083     // Abort on listers without jobs -after- showing the full dump. Easier debugging.
2084     for (KCoreDirLister *listit : std::as_const(listersWithoutJob)) {
2085         qCWarning(KIO_CORE) << "Fatal Error: HUH? Lister" << listit << "is supposed to be listing, but has no job!";
2086         abort();
2087     }
2088 }
2089 #endif
2090 
KCoreDirLister(QObject * parent)2091 KCoreDirLister::KCoreDirLister(QObject *parent)
2092     : QObject(parent)
2093     , d(new KCoreDirListerPrivate(this))
2094 {
2095     qCDebug(KIO_CORE_DIRLISTER) << "+KCoreDirLister";
2096 
2097     d->complete = true;
2098 
2099     setAutoUpdate(true);
2100     setDirOnlyMode(false);
2101     setShowingDotFiles(false);
2102 }
2103 
~KCoreDirLister()2104 KCoreDirLister::~KCoreDirLister()
2105 {
2106     qCDebug(KIO_CORE_DIRLISTER) << "~KCoreDirLister" << this;
2107 
2108     // Stop all running jobs, remove lister from lists
2109     if (!kDirListerCache.isDestroyed()) {
2110         stop();
2111         kDirListerCache()->forgetDirs(this);
2112     }
2113 }
2114 
2115 // TODO KF6: remove bool ret val, it's always true
openUrl(const QUrl & _url,OpenUrlFlags _flags)2116 bool KCoreDirLister::openUrl(const QUrl &_url, OpenUrlFlags _flags)
2117 {
2118     // emit the current changes made to avoid an inconsistent treeview
2119     if (d->hasPendingChanges && (_flags & Keep)) {
2120         emitChanges();
2121     }
2122 
2123     d->hasPendingChanges = false;
2124 
2125     return kDirListerCache()->listDir(this, _url, _flags & Keep, _flags & Reload);
2126 }
2127 
stop()2128 void KCoreDirLister::stop()
2129 {
2130     kDirListerCache()->stop(this);
2131 }
2132 
stop(const QUrl & _url)2133 void KCoreDirLister::stop(const QUrl &_url)
2134 {
2135     kDirListerCache()->stopListingUrl(this, _url);
2136 }
2137 
autoUpdate() const2138 bool KCoreDirLister::autoUpdate() const
2139 {
2140     return d->autoUpdate;
2141 }
2142 
setAutoUpdate(bool enable)2143 void KCoreDirLister::setAutoUpdate(bool enable)
2144 {
2145     if (d->autoUpdate == enable) {
2146         return;
2147     }
2148 
2149     d->autoUpdate = enable;
2150     kDirListerCache()->setAutoUpdate(this, enable);
2151 }
2152 
showingDotFiles() const2153 bool KCoreDirLister::showingDotFiles() const
2154 {
2155     return d->settings.isShowingDotFiles;
2156 }
2157 
setShowingDotFiles(bool showDotFiles)2158 void KCoreDirLister::setShowingDotFiles(bool showDotFiles)
2159 {
2160     if (d->settings.isShowingDotFiles == showDotFiles) {
2161         return;
2162     }
2163 
2164     d->prepareForSettingsChange();
2165     d->settings.isShowingDotFiles = showDotFiles;
2166 }
2167 
dirOnlyMode() const2168 bool KCoreDirLister::dirOnlyMode() const
2169 {
2170     return d->settings.dirOnlyMode;
2171 }
2172 
setDirOnlyMode(bool dirsOnly)2173 void KCoreDirLister::setDirOnlyMode(bool dirsOnly)
2174 {
2175     if (d->settings.dirOnlyMode == dirsOnly) {
2176         return;
2177     }
2178 
2179     d->prepareForSettingsChange();
2180     d->settings.dirOnlyMode = dirsOnly;
2181 }
2182 
requestMimeTypeWhileListing() const2183 bool KCoreDirLister::requestMimeTypeWhileListing() const
2184 {
2185     return d->requestMimeTypeWhileListing;
2186 }
2187 
setRequestMimeTypeWhileListing(bool fromSlave)2188 void KCoreDirLister::setRequestMimeTypeWhileListing(bool fromSlave)
2189 {
2190     if (d->requestMimeTypeWhileListing == fromSlave) {
2191         return;
2192     }
2193 
2194     d->requestMimeTypeWhileListing = fromSlave;
2195     if (d->requestMimeTypeWhileListing) {
2196         // Changing from request off to on, clear any cached items associated
2197         // with this lister so we re-request them and get the mimetype as well.
2198         // If we do not, we risk caching items that have no mime type.
2199         kDirListerCache()->forgetDirs(this);
2200     }
2201 }
2202 
url() const2203 QUrl KCoreDirLister::url() const
2204 {
2205     return d->url;
2206 }
2207 
directories() const2208 QList<QUrl> KCoreDirLister::directories() const
2209 {
2210     return d->lstDirs;
2211 }
2212 
emitChanges()2213 void KCoreDirLister::emitChanges()
2214 {
2215     d->emitChanges();
2216 }
2217 
emitChanges()2218 void KCoreDirListerPrivate::emitChanges()
2219 {
2220     if (!hasPendingChanges) {
2221         return;
2222     }
2223 
2224     // reset 'hasPendingChanges' now, in case of recursion
2225     // (testcase: enabling recursive scan in ktorrent, #174920)
2226     hasPendingChanges = false;
2227 
2228     const KCoreDirListerPrivate::FilterSettings newSettings = settings;
2229     settings = oldSettings; // temporarily
2230 
2231     // Fill hash with all items that are currently visible
2232     std::set<QString> oldVisibleItems;
2233     for (const QUrl &dir : std::as_const(lstDirs)) {
2234         const QList<KFileItem> *itemList = kDirListerCache()->itemsForDir(dir);
2235         if (!itemList) {
2236             continue;
2237         }
2238 
2239         for (const KFileItem &item : *itemList) {
2240             if (isItemVisible(item) && q->matchesMimeFilter(item)) {
2241                 oldVisibleItems.insert(item.name());
2242             }
2243         }
2244     }
2245 
2246     settings = newSettings;
2247 
2248     const QList<QUrl> dirs = lstDirs;
2249     for (const QUrl &dir : dirs) {
2250         KFileItemList deletedItems;
2251 
2252         const QList<KFileItem> *itemList = kDirListerCache()->itemsForDir(dir);
2253         if (!itemList) {
2254             continue;
2255         }
2256 
2257         auto kit = itemList->begin();
2258         const auto kend = itemList->end();
2259         for (; kit != kend; ++kit) {
2260             const KFileItem &item = *kit;
2261             const QString text = item.text();
2262             if (text == QLatin1Char('.') || text == QLatin1String("..")) {
2263                 continue;
2264             }
2265             const bool wasVisible = oldVisibleItems.find(item.name()) != oldVisibleItems.cend();
2266             const bool nowVisible = isItemVisible(item) && q->matchesMimeFilter(item);
2267             if (nowVisible && !wasVisible) {
2268                 addNewItem(dir, item); // takes care of emitting newItem or itemsFilteredByMime
2269             } else if (!nowVisible && wasVisible) {
2270                 deletedItems.append(*kit);
2271             }
2272         }
2273         if (!deletedItems.isEmpty()) {
2274             Q_EMIT q->itemsDeleted(deletedItems);
2275         }
2276         emitItems();
2277     }
2278     oldSettings = settings;
2279 }
2280 
updateDirectory(const QUrl & dirUrl)2281 void KCoreDirLister::updateDirectory(const QUrl &dirUrl)
2282 {
2283     kDirListerCache()->updateDirectory(dirUrl);
2284 }
2285 
isFinished() const2286 bool KCoreDirLister::isFinished() const
2287 {
2288     return d->complete;
2289 }
2290 
rootItem() const2291 KFileItem KCoreDirLister::rootItem() const
2292 {
2293     return d->rootFileItem;
2294 }
2295 
findByUrl(const QUrl & url) const2296 KFileItem KCoreDirLister::findByUrl(const QUrl &url) const
2297 {
2298     return kDirListerCache()->findByUrl(this, url);
2299 }
2300 
findByName(const QString & name) const2301 KFileItem KCoreDirLister::findByName(const QString &name) const
2302 {
2303     return kDirListerCache()->findByName(this, name);
2304 }
2305 
2306 // ================ public filter methods ================ //
2307 
unanchoredPattern(const QString & filter)2308 static QString unanchoredPattern(const QString &filter)
2309 {
2310     const QLatin1String prefix("\\A(?:");
2311     const QLatin1String suffix(")\\z");
2312 
2313     // TODO KF6: QRegularExpression::wildcardToRegularExpression() has an
2314     // option to return an unanchored pattern
2315     QString pattern = QRegularExpression::wildcardToRegularExpression(filter);
2316     if (pattern.startsWith(prefix) && pattern.endsWith(suffix)) {
2317         pattern.remove(0, prefix.size());
2318         pattern.chop(suffix.size());
2319     }
2320 
2321     return pattern;
2322 }
2323 
setNameFilter(const QString & nameFilter)2324 void KCoreDirLister::setNameFilter(const QString &nameFilter)
2325 {
2326     if (d->nameFilter == nameFilter) {
2327         return;
2328     }
2329 
2330     d->prepareForSettingsChange();
2331 
2332     d->settings.lstFilters.clear();
2333     d->nameFilter = nameFilter;
2334     // Split on white space
2335     const QStringList list = nameFilter.split(QLatin1Char(' '), Qt::SkipEmptyParts);
2336     for (const QString &filter : list) {
2337         d->settings.lstFilters.append(QRegularExpression(unanchoredPattern(filter), QRegularExpression::CaseInsensitiveOption));
2338     }
2339 }
2340 
nameFilter() const2341 QString KCoreDirLister::nameFilter() const
2342 {
2343     return d->nameFilter;
2344 }
2345 
setMimeFilter(const QStringList & mimeFilter)2346 void KCoreDirLister::setMimeFilter(const QStringList &mimeFilter)
2347 {
2348     if (d->settings.mimeFilter == mimeFilter) {
2349         return;
2350     }
2351 
2352     d->prepareForSettingsChange();
2353     if (mimeFilter.contains(QLatin1String("application/octet-stream")) || mimeFilter.contains(QLatin1String("all/allfiles"))) { // all files
2354         d->settings.mimeFilter.clear();
2355     } else {
2356         d->settings.mimeFilter = mimeFilter;
2357     }
2358 }
2359 
setMimeExcludeFilter(const QStringList & mimeExcludeFilter)2360 void KCoreDirLister::setMimeExcludeFilter(const QStringList &mimeExcludeFilter)
2361 {
2362     if (d->settings.mimeExcludeFilter == mimeExcludeFilter) {
2363         return;
2364     }
2365 
2366     d->prepareForSettingsChange();
2367     d->settings.mimeExcludeFilter = mimeExcludeFilter;
2368 }
2369 
clearMimeFilter()2370 void KCoreDirLister::clearMimeFilter()
2371 {
2372     d->prepareForSettingsChange();
2373     d->settings.mimeFilter.clear();
2374     d->settings.mimeExcludeFilter.clear();
2375 }
2376 
mimeFilters() const2377 QStringList KCoreDirLister::mimeFilters() const
2378 {
2379     return d->settings.mimeFilter;
2380 }
2381 
matchesFilter(const QString & name) const2382 bool KCoreDirLister::matchesFilter(const QString &name) const
2383 {
2384     return std::any_of(d->settings.lstFilters.cbegin(), d->settings.lstFilters.cend(), [&name](const QRegularExpression &filter) {
2385         return filter.match(name).hasMatch();
2386     });
2387 }
2388 
matchesMimeFilter(const QString & mime) const2389 bool KCoreDirLister::matchesMimeFilter(const QString &mime) const
2390 {
2391     return doMimeFilter(mime, d->settings.mimeFilter) && d->doMimeExcludeFilter(mime, d->settings.mimeExcludeFilter);
2392 }
2393 
2394 // ================ protected methods ================ //
2395 
matchesFilter(const KFileItem & item) const2396 bool KCoreDirLister::matchesFilter(const KFileItem &item) const
2397 {
2398     Q_ASSERT(!item.isNull());
2399 
2400     if (item.text() == QLatin1String("..")) {
2401         return false;
2402     }
2403 
2404     if (!d->settings.isShowingDotFiles && item.isHidden()) {
2405         return false;
2406     }
2407 
2408     if (item.isDir() || d->settings.lstFilters.isEmpty()) {
2409         return true;
2410     }
2411 
2412     return matchesFilter(item.text());
2413 }
2414 
matchesMimeFilter(const KFileItem & item) const2415 bool KCoreDirLister::matchesMimeFilter(const KFileItem &item) const
2416 {
2417     Q_ASSERT(!item.isNull());
2418     // Don't lose time determining the MIME type if there is no filter
2419     if (d->settings.mimeFilter.isEmpty() && d->settings.mimeExcludeFilter.isEmpty()) {
2420         return true;
2421     }
2422     return matchesMimeFilter(item.mimetype());
2423 }
2424 
doNameFilter(const QString & name,const QList<QRegExp> & filters) const2425 bool KCoreDirLister::doNameFilter(const QString &name, const QList<QRegExp> &filters) const
2426 {
2427     return std::any_of(filters.cbegin(), filters.cend(), [&name](const QRegExp &filter) {
2428         return filter.exactMatch(name);
2429     });
2430 }
2431 
doMimeFilter(const QString & mime,const QStringList & filters) const2432 bool KCoreDirLister::doMimeFilter(const QString &mime, const QStringList &filters) const
2433 {
2434     if (filters.isEmpty()) {
2435         return true;
2436     }
2437 
2438     QMimeDatabase db;
2439     const QMimeType mimeptr = db.mimeTypeForName(mime);
2440     if (!mimeptr.isValid()) {
2441         return false;
2442     }
2443 
2444     qCDebug(KIO_CORE_DIRLISTER) << "doMimeFilter: investigating:" << mimeptr.name();
2445     return std::any_of(filters.cbegin(), filters.cend(), [&mimeptr](const QString &filter) {
2446         return mimeptr.inherits(filter);
2447     });
2448 }
2449 
doMimeExcludeFilter(const QString & mime,const QStringList & filters) const2450 bool KCoreDirListerPrivate::doMimeExcludeFilter(const QString &mime, const QStringList &filters) const
2451 {
2452     return !std::any_of(filters.cbegin(), filters.cend(), [&mime](const QString &filter) {
2453         return mime == filter;
2454     });
2455 }
2456 
2457 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 82)
handleError(KIO::Job * job)2458 void KCoreDirLister::handleError(KIO::Job *job)
2459 {
2460     qCWarning(KIO_CORE) << job->errorString();
2461 }
2462 #endif
2463 
2464 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 81)
handleErrorMessage(const QString & message)2465 void KCoreDirLister::handleErrorMessage(const QString &message)
2466 {
2467     qCWarning(KIO_CORE) << message;
2468 }
2469 #endif
2470 
2471 // ================= private methods ================= //
2472 
addNewItem(const QUrl & directoryUrl,const KFileItem & item)2473 void KCoreDirListerPrivate::addNewItem(const QUrl &directoryUrl, const KFileItem &item)
2474 {
2475     if (!isItemVisible(item)) {
2476         return; // No reason to continue... bailing out here prevents a MIME type scan.
2477     }
2478 
2479     qCDebug(KIO_CORE_DIRLISTER) << "in" << directoryUrl << "item:" << item.url();
2480 
2481     if (q->matchesMimeFilter(item)) {
2482         Q_ASSERT(!item.isNull());
2483         lstNewItems[directoryUrl].append(item); // items not filtered
2484     } else {
2485         Q_ASSERT(!item.isNull());
2486         lstMimeFilteredItems.append(item); // only filtered by MIME type
2487     }
2488 }
2489 
addNewItems(const QUrl & directoryUrl,const QList<KFileItem> & items)2490 void KCoreDirListerPrivate::addNewItems(const QUrl &directoryUrl, const QList<KFileItem> &items)
2491 {
2492     // TODO: make this faster - test if we have a filter at all first
2493     // DF: was this profiled? The matchesFoo() functions should be fast, w/o filters...
2494     // Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good.
2495     auto kit = items.cbegin();
2496     const auto kend = items.cend();
2497     for (; kit != kend; ++kit) {
2498         addNewItem(directoryUrl, *kit);
2499     }
2500 }
2501 
addRefreshItem(const QUrl & directoryUrl,const KFileItem & oldItem,const KFileItem & item)2502 void KCoreDirListerPrivate::addRefreshItem(const QUrl &directoryUrl, const KFileItem &oldItem, const KFileItem &item)
2503 {
2504     // Refreshing the root item "." of a dirlister
2505     if (directoryUrl == item.url()) {
2506         lstRefreshItems.append({oldItem, item});
2507         return;
2508     }
2509 
2510     const bool refreshItemWasFiltered = !isItemVisible(oldItem) || !q->matchesMimeFilter(oldItem);
2511     if (isItemVisible(item) && q->matchesMimeFilter(item)) {
2512         if (refreshItemWasFiltered) {
2513             Q_ASSERT(!item.isNull());
2514             lstNewItems[directoryUrl].append(item);
2515         } else {
2516             Q_ASSERT(!item.isNull());
2517             lstRefreshItems.append(qMakePair(oldItem, item));
2518         }
2519     } else if (!refreshItemWasFiltered) {
2520         // notify the user that the MIME type of a file changed that doesn't match
2521         // a filter or does match an exclude filter
2522         // This also happens when renaming foo to .foo and dot files are hidden (#174721)
2523         Q_ASSERT(!oldItem.isNull());
2524         lstRemoveItems.append(oldItem);
2525     }
2526 }
2527 
emitItems()2528 void KCoreDirListerPrivate::emitItems()
2529 {
2530     if (!lstNewItems.empty()) {
2531         QHashIterator<QUrl, KFileItemList> it(lstNewItems);
2532         while (it.hasNext()) {
2533             it.next();
2534             Q_EMIT q->itemsAdded(it.key(), it.value());
2535             Q_EMIT q->newItems(it.value()); // compat
2536         }
2537         lstNewItems.clear();
2538     }
2539 
2540     if (!lstMimeFilteredItems.empty()) {
2541         Q_EMIT q->itemsFilteredByMime(lstMimeFilteredItems);
2542         lstMimeFilteredItems.clear();
2543     }
2544 
2545     if (!lstRefreshItems.empty()) {
2546         Q_EMIT q->refreshItems(lstRefreshItems);
2547         lstRefreshItems.clear();
2548     }
2549 
2550     if (!lstRemoveItems.empty()) {
2551         Q_EMIT q->itemsDeleted(lstRemoveItems);
2552         lstRemoveItems.clear();
2553     }
2554 }
2555 
isItemVisible(const KFileItem & item) const2556 bool KCoreDirListerPrivate::isItemVisible(const KFileItem &item) const
2557 {
2558     // Note that this doesn't include MIME type filters, because
2559     // of the itemsFilteredByMime signal. Filtered-by-MIME-type items are
2560     // considered "visible", they are just visible via a different signal...
2561     return (!settings.dirOnlyMode || item.isDir()) && q->matchesFilter(item);
2562 }
2563 
emitItemsDeleted(const KFileItemList & itemsList)2564 void KCoreDirListerPrivate::emitItemsDeleted(const KFileItemList &itemsList)
2565 {
2566     KFileItemList items = itemsList;
2567     QMutableListIterator<KFileItem> it(items);
2568     while (it.hasNext()) {
2569         const KFileItem &item = it.next();
2570         if (!isItemVisible(item) || !q->matchesMimeFilter(item)) {
2571             it.remove();
2572         }
2573     }
2574     if (!items.isEmpty()) {
2575         Q_EMIT q->itemsDeleted(items);
2576     }
2577 }
2578 
KCoreDirListerPrivate(KCoreDirLister * qq)2579 KCoreDirListerPrivate::KCoreDirListerPrivate(KCoreDirLister *qq)
2580     : q(qq)
2581 {
2582 }
2583 
2584 // ================ private slots ================ //
2585 
slotInfoMessage(KJob *,const QString & message)2586 void KCoreDirListerPrivate::slotInfoMessage(KJob *, const QString &message)
2587 {
2588     Q_EMIT q->infoMessage(message);
2589 }
2590 
slotPercent(KJob * job,unsigned long pcnt)2591 void KCoreDirListerPrivate::slotPercent(KJob *job, unsigned long pcnt)
2592 {
2593     jobData[static_cast<KIO::ListJob *>(job)].percent = pcnt;
2594 
2595     int result = 0;
2596 
2597     KIO::filesize_t size = 0;
2598 
2599     auto dataIt = jobData.cbegin();
2600     while (dataIt != jobData.cend()) {
2601         const auto data = dataIt.value();
2602         result += data.percent * data.totalSize;
2603         size += data.totalSize;
2604         ++dataIt;
2605     }
2606 
2607     if (size != 0) {
2608         result /= size;
2609     } else {
2610         result = 100;
2611     }
2612     Q_EMIT q->percent(result);
2613 }
2614 
slotTotalSize(KJob * job,qulonglong size)2615 void KCoreDirListerPrivate::slotTotalSize(KJob *job, qulonglong size)
2616 {
2617     jobData[static_cast<KIO::ListJob *>(job)].totalSize = size;
2618 
2619     KIO::filesize_t result = 0;
2620     auto dataIt = jobData.cbegin();
2621     while (dataIt != jobData.cend()) {
2622         result += dataIt.value().totalSize;
2623         ++dataIt;
2624     }
2625 
2626     Q_EMIT q->totalSize(result);
2627 }
2628 
slotProcessedSize(KJob * job,qulonglong size)2629 void KCoreDirListerPrivate::slotProcessedSize(KJob *job, qulonglong size)
2630 {
2631     jobData[static_cast<KIO::ListJob *>(job)].processedSize = size;
2632 
2633     KIO::filesize_t result = 0;
2634     auto dataIt = jobData.cbegin();
2635     while (dataIt != jobData.cend()) {
2636         result += dataIt.value().processedSize;
2637         ++dataIt;
2638     }
2639 
2640     Q_EMIT q->processedSize(result);
2641 }
2642 
slotSpeed(KJob * job,unsigned long spd)2643 void KCoreDirListerPrivate::slotSpeed(KJob *job, unsigned long spd)
2644 {
2645     jobData[static_cast<KIO::ListJob *>(job)].speed = spd;
2646 
2647     int result = 0;
2648     auto dataIt = jobData.cbegin();
2649     while (dataIt != jobData.cend()) {
2650         result += dataIt.value().speed;
2651         ++dataIt;
2652     }
2653 
2654     Q_EMIT q->speed(result);
2655 }
2656 
numJobs()2657 uint KCoreDirListerPrivate::numJobs()
2658 {
2659 #ifdef DEBUG_CACHE
2660     // This code helps detecting stale entries in the jobData map.
2661     qCDebug(KIO_CORE_DIRLISTER) << q << "numJobs:" << jobData.count();
2662     QMapIterator<KIO::ListJob *, JobData> it(jobData);
2663     while (it.hasNext()) {
2664         it.next();
2665         qCDebug(KIO_CORE_DIRLISTER) << (void *)it.key();
2666         qCDebug(KIO_CORE_DIRLISTER) << it.key();
2667     }
2668 #endif
2669 
2670     return jobData.count();
2671 }
2672 
jobDone(KIO::ListJob * job)2673 void KCoreDirListerPrivate::jobDone(KIO::ListJob *job)
2674 {
2675     jobData.remove(job);
2676 }
2677 
jobStarted(KIO::ListJob * job)2678 void KCoreDirLister::jobStarted(KIO::ListJob *job)
2679 {
2680     KCoreDirListerPrivate::JobData data;
2681     data.speed = 0;
2682     data.percent = 0;
2683     data.processedSize = 0;
2684     data.totalSize = 0;
2685 
2686     d->jobData.insert(job, data);
2687     d->complete = false;
2688 }
2689 
connectJob(KIO::ListJob * job)2690 void KCoreDirListerPrivate::connectJob(KIO::ListJob *job)
2691 {
2692     q->connect(job, &KJob::infoMessage, q, [this](KJob *job, const QString &plain) {
2693         slotInfoMessage(job, plain);
2694     });
2695 
2696     q->connect(job, &KJob::percentChanged, q, [this](KJob *job, ulong _percent) {
2697         slotPercent(job, _percent);
2698     });
2699 
2700     q->connect(job, &KJob::totalSize, q, [this](KJob *job, qulonglong _size) {
2701         slotTotalSize(job, _size);
2702     });
2703     q->connect(job, &KJob::processedSize, q, [this](KJob *job, qulonglong _psize) {
2704         slotProcessedSize(job, _psize);
2705     });
2706     q->connect(job, &KJob::speed, q, [this](KJob *job, qulonglong _speed) {
2707         slotSpeed(job, _speed);
2708     });
2709 }
2710 
items(WhichItems which) const2711 KFileItemList KCoreDirLister::items(WhichItems which) const
2712 {
2713     return itemsForDir(url(), which);
2714 }
2715 
itemsForDir(const QUrl & dir,WhichItems which) const2716 KFileItemList KCoreDirLister::itemsForDir(const QUrl &dir, WhichItems which) const
2717 {
2718     QList<KFileItem> *allItems = kDirListerCache()->itemsForDir(dir);
2719     KFileItemList result;
2720     if (!allItems) {
2721         return result;
2722     }
2723 
2724     if (which == AllItems) {
2725         return KFileItemList(*allItems);
2726     } else { // only items passing the filters
2727         auto kit = allItems->constBegin();
2728         const auto kend = allItems->constEnd();
2729         for (; kit != kend; ++kit) {
2730             const KFileItem &item = *kit;
2731             if (d->isItemVisible(item) && matchesMimeFilter(item)) {
2732                 result.append(item);
2733             }
2734         }
2735     }
2736     return result;
2737 }
2738 
delayedMimeTypes() const2739 bool KCoreDirLister::delayedMimeTypes() const
2740 {
2741     return d->delayedMimeTypes;
2742 }
2743 
setDelayedMimeTypes(bool delayedMimeTypes)2744 void KCoreDirLister::setDelayedMimeTypes(bool delayedMimeTypes)
2745 {
2746     d->delayedMimeTypes = delayedMimeTypes;
2747 }
2748 
2749 // called by KCoreDirListerCache::slotRedirection
redirect(const QUrl & oldUrl,const QUrl & newUrl,bool keepItems)2750 void KCoreDirListerPrivate::redirect(const QUrl &oldUrl, const QUrl &newUrl, bool keepItems)
2751 {
2752     if (url.matches(oldUrl, QUrl::StripTrailingSlash)) {
2753         if (!keepItems) {
2754             rootFileItem = KFileItem();
2755         } else {
2756             rootFileItem.setUrl(newUrl);
2757         }
2758         url = newUrl;
2759     }
2760 
2761     const int idx = lstDirs.indexOf(oldUrl);
2762     if (idx == -1) {
2763         qCWarning(KIO_CORE) << "Unexpected redirection from" << oldUrl << "to" << newUrl << "but this dirlister is currently listing/holding" << lstDirs;
2764     } else {
2765         lstDirs[idx] = newUrl;
2766     }
2767 
2768     if (lstDirs.count() == 1) {
2769         if (!keepItems) {
2770             Q_EMIT q->clear();
2771         }
2772 
2773 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 80)
2774         Q_EMIT q->redirection(newUrl);
2775 #endif
2776 
2777     } else {
2778         if (!keepItems) {
2779 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
2780             Q_EMIT q->clear(oldUrl);
2781 #endif
2782             Q_EMIT q->clearDir(oldUrl);
2783         }
2784     }
2785     Q_EMIT q->redirection(oldUrl, newUrl);
2786 }
2787 
moveListersWithoutCachedItemsJob(const QUrl & url)2788 void KCoreDirListerCacheDirectoryData::moveListersWithoutCachedItemsJob(const QUrl &url)
2789 {
2790     // Move dirlisters from listersCurrentlyListing to listersCurrentlyHolding,
2791     // but not those that are still waiting on a CachedItemsJob...
2792     // Unit-testing note:
2793     // Run kdirmodeltest in valgrind to hit the case where an update
2794     // is triggered while a lister has a CachedItemsJob (different timing...)
2795     QMutableListIterator<KCoreDirLister *> lister_it(listersCurrentlyListing);
2796     while (lister_it.hasNext()) {
2797         KCoreDirLister *kdl = lister_it.next();
2798         if (!kdl->d->cachedItemsJobForUrl(url)) {
2799             // OK, move this lister from "currently listing" to "currently holding".
2800 
2801             // Huh? The KCoreDirLister was present twice in listersCurrentlyListing, or was in both lists?
2802             Q_ASSERT(!listersCurrentlyHolding.contains(kdl));
2803             if (!listersCurrentlyHolding.contains(kdl)) {
2804                 listersCurrentlyHolding.append(kdl);
2805             }
2806             lister_it.remove();
2807         } else {
2808             qCDebug(KIO_CORE_DIRLISTER) << "Not moving" << kdl << "to listersCurrentlyHolding because it still has job" << kdl->d->m_cachedItemsJobs;
2809         }
2810     }
2811 }
2812 
cachedItemForUrl(const QUrl & url)2813 KFileItem KCoreDirLister::cachedItemForUrl(const QUrl &url)
2814 {
2815     if (kDirListerCache.exists()) {
2816         return kDirListerCache()->itemForUrl(url);
2817     } else {
2818         return {};
2819     }
2820 }
2821 
autoErrorHandlingEnabled() const2822 bool KCoreDirLister::autoErrorHandlingEnabled() const
2823 {
2824     return d->m_autoErrorHandling;
2825 }
2826 
setAutoErrorHandlingEnabled(bool enable)2827 void KCoreDirLister::setAutoErrorHandlingEnabled(bool enable)
2828 {
2829     d->m_autoErrorHandling = enable;
2830 }
2831 
cachedDotHiddenForDir(const QString & dir)2832 KCoreDirListerCache::CacheHiddenFile *KCoreDirListerCache::cachedDotHiddenForDir(const QString &dir)
2833 {
2834     const QString path = dir + QLatin1String("/.hidden");
2835     QFile dotHiddenFile(path);
2836 
2837     if (dotHiddenFile.exists()) {
2838         const QDateTime mtime = QFileInfo(dotHiddenFile).lastModified();
2839         CacheHiddenFile *cachedDotHiddenFile = m_cacheHiddenFiles.object(path);
2840 
2841         if (cachedDotHiddenFile && mtime <= cachedDotHiddenFile->mtime) {
2842             // ".hidden" is in cache and still valid (the file was not modified since then),
2843             // so return it
2844             return cachedDotHiddenFile;
2845         } else {
2846             // read the ".hidden" file, then cache it and return it
2847             if (dotHiddenFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
2848                 std::set<QString> filesToHide;
2849                 QTextStream stream(&dotHiddenFile);
2850                 while (!stream.atEnd()) {
2851                     QString name = stream.readLine();
2852                     if (!name.isEmpty()) {
2853                         filesToHide.insert(name);
2854                     }
2855                 }
2856 
2857                 m_cacheHiddenFiles.insert(path, new CacheHiddenFile(mtime, std::move(filesToHide)));
2858 
2859                 return m_cacheHiddenFiles.object(path);
2860             }
2861         }
2862     }
2863 
2864     return {};
2865 }
2866 
2867 #include "moc_kcoredirlister.cpp"
2868 #include "moc_kcoredirlister_p.cpp"
2869