KIO

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

KDE's Doxygen guidelines are available online.