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
1785 bool inPendingUpdates = false;
1786 bool inPendingRemoteUpdates = false;
1787
1788 std::set<QString>::iterator pu_it;
1789 std::set<KFileItem>::iterator pru_it;
1790 if (tmp.url().isLocalFile()) {
1791 pu_it = pendingUpdates.find(tmp.url().toLocalFile());
1792 inPendingUpdates = pu_it != pendingUpdates.end();
1793 } else {
1794 pru_it = pendingRemoteUpdates.find(tmp);
1795 inPendingRemoteUpdates = pru_it != pendingRemoteUpdates.end();
1796 }
1797
1798 // check if something changed for this file, using KFileItem::cmp()
1799 if (inPendingRemoteUpdates || inPendingUpdates || !tmp.cmp(item)) {
1800 if (inPendingRemoteUpdates) {
1801 pendingRemoteUpdates.erase(pru_it);
1802 }
1803 if (inPendingUpdates) {
1804 pendingUpdates.erase(pu_it);
1805 }
1806
1807 qCDebug(KIO_CORE_DIRLISTER) << "file changed:" << tmp.name();
1808
1809 reinsert(item, tmp.url());
1810 for (KCoreDirLister *kdl : listers) {
1811 kdl->d->addRefreshItem(jobUrl, tmp, item);
1812 }
1813 }
1814 // Seen, remove
1815 fileItems.erase(fiit);
1816 } else { // this is a new file
1817 qCDebug(KIO_CORE_DIRLISTER) << "new file:" << name;
1818 newItems.append(item);
1819 }
1820 }
1821
1822 // sort by url using KFileItem::operator<
1823 std::sort(newItems.begin(), newItems.end());
1824
1825 // Add the items sorted by url, needed by findByUrl
1826 dir->insertSortedItems(newItems);
1827
1828 for (KCoreDirLister *kdl : listers) {
1829 kdl->d->addNewItems(jobUrl, newItems);
1830 }
1831
1832 runningListJobs.remove(job);
1833
1834 if (!fileItems.isEmpty()) {
1835 deleteUnmarkedItems(listers, dir->lstItems, fileItems);
1836 }
1837
1838 for (KCoreDirLister *kdl : listers) {
1839 kdl->d->emitItems();
1840
1841 kdl->d->jobDone(job);
1842 Q_EMIT kdl->listingDirCompleted(jobUrl);
1843 if (kdl->d->numJobs() == 0) {
1844 kdl->d->complete = true;
1845 Q_EMIT kdl->completed();
1846 }
1847 }
1848
1849 // TODO: hmm, if there was an error and job is a parent of one or more
1850 // of the pending urls we should cancel it/them as well
1851 processPendingUpdates();
1852
1853 if (job->property("need_another_update").toBool()) {
1854 updateDirectory(jobUrl);
1855 }
1856}
1857
1858// private
1859
1860KIO::ListJob *KCoreDirListerCache::jobForUrl(const QUrl &url, KIO::ListJob *not_job)
1861{
1862 for (auto it = runningListJobs.cbegin(); it != runningListJobs.cend(); ++it) {
1863 KIO::ListJob *job = it.key();
1864 const QUrl jobUrl = joburl(job).adjusted(QUrl::StripTrailingSlash);
1865
1866 if (jobUrl == url && job != not_job) {
1867 return job;
1868 }
1869 }
1870 return nullptr;
1871}
1872
1873const QUrl &KCoreDirListerCache::joburl(KIO::ListJob *job)
1874{
1875 if (job->redirectionUrl().isValid()) {
1876 return job->redirectionUrl();
1877 } else {
1878 return job->url();
1879 }
1880}
1881
1882void KCoreDirListerCache::killJob(KIO::ListJob *job)
1883{
1884 runningListJobs.remove(job);
1885 job->disconnect(this);
1886 job->kill();
1887}
1888
1889void KCoreDirListerCache::deleteUnmarkedItems(const QList<KCoreDirLister *> &listers,
1890 QList<KFileItem> &lstItems,
1891 const QHash<QString, KFileItem> &itemsToDelete)
1892{
1893 // Make list of deleted items (for emitting)
1894 KFileItemList deletedItems;
1895 deletedItems.reserve(itemsToDelete.size());
1896 for (auto kit = itemsToDelete.cbegin(), endIt = itemsToDelete.cend(); kit != endIt; ++kit) {
1897 const KFileItem item = kit.value();
1898 deletedItems.append(item);
1899 qCDebug(KIO_CORE_DIRLISTER) << "deleted:" << item.name() << item;
1900 }
1901
1902 // Delete all remaining items
1903 auto it = std::remove_if(lstItems.begin(), lstItems.end(), [&itemsToDelete](const KFileItem &item) {
1904 return itemsToDelete.contains(item.name());
1905 });
1906 lstItems.erase(it, lstItems.end());
1907
1908 itemsDeleted(listers, deletedItems);
1909}
1910
1911void KCoreDirListerCache::itemsDeleted(const QList<KCoreDirLister *> &listers, const KFileItemList &deletedItems)
1912{
1913 for (KCoreDirLister *kdl : listers) {
1914 kdl->d->emitItemsDeleted(deletedItems);
1915 }
1916
1917 for (const KFileItem &item : deletedItems) {
1918 if (item.isDir()) {
1919 deleteDir(item.url());
1920 }
1921 }
1922}
1923
1924void KCoreDirListerCache::deleteDir(const QUrl &_dirUrl)
1925{
1926 qCDebug(KIO_CORE_DIRLISTER) << _dirUrl;
1927 // unregister and remove the children of the deleted item.
1928 // Idea: tell all the KCoreDirListers that they should forget the dir
1929 // and then remove it from the cache.
1930
1931 QUrl dirUrl(_dirUrl.adjusted(QUrl::StripTrailingSlash));
1932
1933 // Separate itemsInUse iteration and calls to forgetDirs (which modify itemsInUse)
1934 QList<QUrl> affectedItems;
1935
1936 auto itu = itemsInUse.cbegin();
1937 const auto ituend = itemsInUse.cend();
1938 for (; itu != ituend; ++itu) {
1939 const QUrl &deletedUrl = itu.key();
1940 if (dirUrl == deletedUrl || dirUrl.isParentOf(deletedUrl)) {
1941 affectedItems.append(deletedUrl);
1942 }
1943 }
1944
1945 for (const QUrl &deletedUrl : std::as_const(affectedItems)) {
1946 // stop all jobs for deletedUrlStr
1947 auto dit = directoryData.constFind(deletedUrl);
1948 if (dit != directoryData.cend()) {
1949 // we need a copy because stop modifies the list
1950 const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing;
1951 for (KCoreDirLister *kdl : listers) {
1952 stopListingUrl(kdl, deletedUrl);
1953 }
1954 // tell listers holding deletedUrl to forget about it
1955 // this will stop running updates for deletedUrl as well
1956
1957 // we need a copy because forgetDirs modifies the list
1958 const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding;
1959 for (KCoreDirLister *kdl : holders) {
1960 // lister's root is the deleted item
1961 if (kdl->d->url == deletedUrl) {
1962 // tell the view first. It might need the subdirs' items (which forgetDirs will delete)
1963 if (!kdl->d->rootFileItem.isNull()) {
1964 Q_EMIT kdl->itemsDeleted(KFileItemList{kdl->d->rootFileItem});
1965 }
1966 forgetDirs(kdl);
1967 kdl->d->rootFileItem = KFileItem();
1968 } else {
1969 const bool treeview = kdl->d->lstDirs.count() > 1;
1970 if (!treeview) {
1971 Q_EMIT kdl->clear();
1972 kdl->d->lstDirs.clear();
1973 } else {
1974 kdl->d->lstDirs.removeAll(deletedUrl);
1975 }
1976
1977 forgetDirs(kdl, deletedUrl, treeview);
1978 }
1979 }
1980 }
1981
1982 // delete the entry for deletedUrl - should not be needed, it's in
1983 // items cached now
1984 int count = itemsInUse.remove(deletedUrl);
1985 Q_ASSERT(count == 0);
1986 Q_UNUSED(count); // keep gcc "unused variable" complaining quiet when in release mode
1987 }
1988
1989 // remove the children from the cache
1990 removeDirFromCache(dirUrl);
1991}
1992
1993// delayed updating of files, FAM is flooding us with events
1994void KCoreDirListerCache::processPendingUpdates()
1995{
1996 std::set<KCoreDirLister *> listers;
1997 QList<QUrl> removedUrls;
1998 for (const QString &file : pendingUpdates) { // always a local path
1999 qCDebug(KIO_CORE_DIRLISTER) << file;
2000 QUrl u = QUrl::fromLocalFile(file);
2001 KFileItem item = findByUrl(nullptr, u); // search all items
2002 if (!item.isNull()) {
2003 // we need to refresh the item, because e.g. the permissions can have changed.
2004 KFileItem oldItem = item;
2005 item.refresh();
2006
2007 if (!oldItem.cmp(item)) {
2008 if (!item.exists()) {
2009 removedUrls.append(oldItem.url());
2010 } else {
2011 reinsert(item, oldItem.url());
2012 }
2013 listers.merge(emitRefreshItem(oldItem, item));
2014 }
2015 }
2016 }
2017 pendingUpdates.clear();
2018 for (KCoreDirLister *kdl : listers) {
2019 kdl->d->emitItems();
2020 }
2021
2022 // clean orphan KFileItem, after events were emitted
2023 for (const auto &removedUrl : removedUrls) {
2024 remove(removedUrl);
2025 }
2026
2027 // Directories in need of updating
2028 for (const QString &dir : pendingDirectoryUpdates) {
2029 updateDirectory(QUrl::fromLocalFile(dir));
2030 }
2031 pendingDirectoryUpdates.clear();
2032}
2033
2034#ifndef NDEBUG
2035void KCoreDirListerCache::printDebug()
2036{
2037 qCDebug(KIO_CORE_DIRLISTER) << "Items in use:";
2038 auto itu = itemsInUse.constBegin();
2039 const auto ituend = itemsInUse.constEnd();
2040 for (; itu != ituend; ++itu) {
2041 qCDebug(KIO_CORE_DIRLISTER) << " " << itu.key() << "URL:" << itu.value()->url
2042 << "rootItem:" << (!itu.value()->rootItem.isNull() ? itu.value()->rootItem.url() : QUrl())
2043 << "autoUpdates refcount:" << itu.value()->autoUpdates << "complete:" << itu.value()->complete
2044 << QStringLiteral("with %1 items.").arg(itu.value()->lstItems.count());
2045 }
2046
2047 QList<KCoreDirLister *> listersWithoutJob;
2048 qCDebug(KIO_CORE_DIRLISTER) << "Directory data:";
2049 auto dit = directoryData.constBegin();
2050 for (; dit != directoryData.constEnd(); ++dit) {
2051 QString list;
2052 const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing;
2053 for (KCoreDirLister *listit : listers) {
2054 list += QLatin1String(" 0x") + QString::number(reinterpret_cast<qlonglong>(listit), 16);
2055 }
2056 qCDebug(KIO_CORE_DIRLISTER) << " " << dit.key() << listers.count() << "listers:" << list;
2057 for (KCoreDirLister *listit : listers) {
2058 if (!listit->d->m_cachedItemsJobs.isEmpty()) {
2059 qCDebug(KIO_CORE_DIRLISTER) << " Lister" << listit << "has CachedItemsJobs" << listit->d->m_cachedItemsJobs;
2060 } else if (KIO::ListJob *listJob = jobForUrl(dit.key())) {
2061 qCDebug(KIO_CORE_DIRLISTER) << " Lister" << listit << "has ListJob" << listJob;
2062 } else {
2063 listersWithoutJob.append(listit);
2064 }
2065 }
2066
2067 list.clear();
2068 const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding;
2069 for (KCoreDirLister *listit : holders) {
2070 list += QLatin1String(" 0x") + QString::number(reinterpret_cast<qlonglong>(listit), 16);
2071 }
2072 qCDebug(KIO_CORE_DIRLISTER) << " " << dit.key() << holders.count() << "holders:" << list;
2073 }
2074
2076 qCDebug(KIO_CORE_DIRLISTER) << "Jobs:";
2077 for (; jit != runningListJobs.end(); ++jit) {
2078 qCDebug(KIO_CORE_DIRLISTER) << " " << jit.key() << "listing" << joburl(jit.key()) << ":" << (*jit).count() << "entries.";
2079 }
2080
2081 qCDebug(KIO_CORE_DIRLISTER) << "Items in cache:";
2082 const QList<QUrl> cachedDirs = itemsCached.keys();
2083 for (const QUrl &cachedDir : cachedDirs) {
2084 DirItem *dirItem = itemsCached.object(cachedDir);
2085 qCDebug(KIO_CORE_DIRLISTER) << " " << cachedDir
2086 << "rootItem:" << (!dirItem->rootItem.isNull() ? dirItem->rootItem.url().toString() : QStringLiteral("NULL")) << "with"
2087 << dirItem->lstItems.count() << "items.";
2088 }
2089
2090 // Abort on listers without jobs -after- showing the full dump. Easier debugging.
2091 for (KCoreDirLister *listit : std::as_const(listersWithoutJob)) {
2092 qCWarning(KIO_CORE) << "Fatal Error: HUH? Lister" << listit << "is supposed to be listing, but has no job!";
2093 abort();
2094 }
2095}
2096#endif
2097
2099 : QObject(parent)
2100 , d(new KCoreDirListerPrivate(this))
2101{
2102 qCDebug(KIO_CORE_DIRLISTER) << "+KCoreDirLister";
2103
2104 d->complete = true;
2105
2106 setAutoUpdate(true);
2107 setDirOnlyMode(false);
2108 setShowHiddenFiles(false);
2109}
2110
2112{
2113 qCDebug(KIO_CORE_DIRLISTER) << "~KCoreDirLister" << this;
2114
2115 // Stop all running jobs, remove lister from lists
2116 if (!qApp->closingDown()) {
2117 stop();
2118 s_kDirListerCache.localData().forgetDirs(this);
2119 }
2120}
2121
2122// TODO KF6: remove bool ret val, it's always true
2124{
2125 // emit the current changes made to avoid an inconsistent treeview
2126 if (d->hasPendingChanges && (_flags & Keep)) {
2127 emitChanges();
2128 }
2129
2130 d->hasPendingChanges = false;
2131
2132 return s_kDirListerCache.localData().listDir(this, _url, _flags & Keep, _flags & Reload);
2133}
2134
2136{
2137 s_kDirListerCache.localData().stop(this);
2138}
2139
2141{
2142 s_kDirListerCache.localData().stopListingUrl(this, _url);
2143}
2144
2146{
2147 s_kDirListerCache.localData().forgetDirs(this, _url, true);
2148}
2149
2150bool KCoreDirLister::autoUpdate() const
2151{
2152 return d->autoUpdate;
2153}
2154
2156{
2157 if (d->autoUpdate == enable) {
2158 return;
2159 }
2160
2161 d->autoUpdate = enable;
2162 s_kDirListerCache.localData().setAutoUpdate(this, enable);
2163}
2164
2165bool KCoreDirLister::showHiddenFiles() const
2166{
2167 return d->settings.isShowingDotFiles;
2168}
2169
2170void KCoreDirLister::setShowHiddenFiles(bool setShowHiddenFiles)
2171{
2172 if (d->settings.isShowingDotFiles == setShowHiddenFiles) {
2173 return;
2174 }
2175
2176 d->prepareForSettingsChange();
2177 d->settings.isShowingDotFiles = setShowHiddenFiles;
2178}
2179
2180bool KCoreDirLister::dirOnlyMode() const
2181{
2182 return d->settings.dirOnlyMode;
2183}
2184
2186{
2187 if (d->settings.dirOnlyMode == dirsOnly) {
2188 return;
2189 }
2190
2191 d->prepareForSettingsChange();
2192 d->settings.dirOnlyMode = dirsOnly;
2193}
2194
2195bool KCoreDirLister::requestMimeTypeWhileListing() const
2196{
2197 return d->requestMimeTypeWhileListing;
2198}
2199
2201{
2202 if (d->requestMimeTypeWhileListing == request) {
2203 return;
2204 }
2205
2206 d->requestMimeTypeWhileListing = request;
2207 if (d->requestMimeTypeWhileListing) {
2208 // Changing from request off to on, clear any cached items associated
2209 // with this lister so we re-request them and get the mimetype as well.
2210 // If we do not, we risk caching items that have no mime type.
2211 s_kDirListerCache.localData().forgetDirs(this);
2212 }
2213}
2214
2216{
2217 return d->url;
2218}
2219
2221{
2222 return d->lstDirs;
2223}
2224
2226{
2227 d->emitChanges();
2228}
2229
2230void KCoreDirListerPrivate::emitChanges()
2231{
2232 if (!hasPendingChanges) {
2233 return;
2234 }
2235
2236 // reset 'hasPendingChanges' now, in case of recursion
2237 // (testcase: enabling recursive scan in ktorrent, #174920)
2238 hasPendingChanges = false;
2239
2240 const KCoreDirListerPrivate::FilterSettings newSettings = settings;
2241 settings = oldSettings; // temporarily
2242
2243 // Fill hash with all items that are currently visible
2244 std::set<QString> oldVisibleItems;
2245 for (const QUrl &dir : std::as_const(lstDirs)) {
2246 const QList<KFileItem> *itemList = s_kDirListerCache.localData().itemsForDir(dir);
2247 if (!itemList) {
2248 continue;
2249 }
2250
2251 for (const KFileItem &item : *itemList) {
2252 if (isItemVisible(item) && matchesMimeFilter(item)) {
2253 oldVisibleItems.insert(item.name());
2254 }
2255 }
2256 }
2257
2258 settings = newSettings;
2259
2260 const QList<QUrl> dirs = lstDirs;
2261 for (const QUrl &dir : dirs) {
2262 KFileItemList deletedItems;
2263
2264 const QList<KFileItem> *itemList = s_kDirListerCache.localData().itemsForDir(dir);
2265 if (!itemList) {
2266 continue;
2267 }
2268
2269 for (const auto &item : *itemList) {
2270 const QString text = item.text();
2271 if (text == QLatin1Char('.') || text == QLatin1String("..")) {
2272 continue;
2273 }
2274 const bool wasVisible = oldVisibleItems.find(item.name()) != oldVisibleItems.cend();
2275 const bool mimeFiltered = matchesMimeFilter(item);
2276 const bool nowVisible = isItemVisible(item) && mimeFiltered;
2277 if (nowVisible && !wasVisible) {
2278 addNewItem(dir, item); // takes care of emitting newItem or itemsFilteredByMime
2279 } else if (!nowVisible && wasVisible) {
2280 if (!mimeFiltered) {
2281 lstMimeFilteredItems.append(item);
2282 }
2283 deletedItems.append(item);
2284 }
2285 }
2286 if (!deletedItems.isEmpty()) {
2287 Q_EMIT q->itemsDeleted(deletedItems);
2288 }
2289 emitItems();
2290 }
2291 oldSettings = settings;
2292}
2293
2295{
2296 s_kDirListerCache.localData().updateDirectory(dirUrl);
2297}
2298
2300{
2301 return d->complete;
2302}
2303
2305{
2306 return d->rootFileItem;
2307}
2308
2310{
2311 return s_kDirListerCache.localData().findByUrl(this, url);
2312}
2313
2315{
2316 return s_kDirListerCache.localData().findByName(this, name);
2317}
2318
2319// ================ public filter methods ================ //
2320
2322{
2323 if (d->nameFilter == nameFilter) {
2324 return;
2325 }
2326
2327 d->prepareForSettingsChange();
2328
2329 d->settings.lstFilters.clear();
2330 d->nameFilter = nameFilter;
2331 // Split on white space
2332 const QList<QStringView> list = QStringView(nameFilter).split(QLatin1Char(' '), Qt::SkipEmptyParts);
2333 for (const QStringView filter : list) {
2335 }
2336}
2337
2338QString KCoreDirLister::nameFilter() const
2339{
2340 return d->nameFilter;
2341}
2342
2344{
2345 if (d->settings.mimeFilter == mimeFilter) {
2346 return;
2347 }
2348
2349 d->prepareForSettingsChange();
2350 if (mimeFilter.contains(QLatin1String("application/octet-stream")) || mimeFilter.contains(QLatin1String("all/allfiles"))) { // all files
2351 d->settings.mimeFilter.clear();
2352 } else {
2353 d->settings.mimeFilter = mimeFilter;
2354 }
2355}
2356
2358{
2359 if (d->settings.mimeExcludeFilter == mimeExcludeFilter) {
2360 return;
2361 }
2362
2363 d->prepareForSettingsChange();
2364 d->settings.mimeExcludeFilter = mimeExcludeFilter;
2365}
2366
2368{
2369 d->prepareForSettingsChange();
2370 d->settings.mimeFilter.clear();
2371 d->settings.mimeExcludeFilter.clear();
2372}
2373
2375{
2376 return d->settings.mimeFilter;
2377}
2378
2379// ================ protected methods ================ //
2380
2381bool KCoreDirListerPrivate::matchesFilter(const KFileItem &item) const
2382{
2383 Q_ASSERT(!item.isNull());
2384
2385 if (item.text() == QLatin1String("..")) {
2386 return false;
2387 }
2388
2389 if (!settings.isShowingDotFiles && item.isHidden()) {
2390 return false;
2391 }
2392
2393 if (item.isDir() || settings.lstFilters.isEmpty()) {
2394 return true;
2395 }
2396
2397 return std::any_of(settings.lstFilters.cbegin(), settings.lstFilters.cend(), [&item](const QRegularExpression &filter) {
2398 return filter.match(item.text()).hasMatch();
2399 });
2400}
2401
2402bool KCoreDirListerPrivate::matchesMimeFilter(const KFileItem &item) const
2403{
2404 Q_ASSERT(!item.isNull());
2405 // Don't lose time determining the MIME type if there is no filter
2406 if (settings.mimeFilter.isEmpty() && settings.mimeExcludeFilter.isEmpty()) {
2407 return true;
2408 }
2409 return doMimeFilter(item.mimetype(), settings.mimeFilter) && doMimeExcludeFilter(item.mimetype(), settings.mimeExcludeFilter);
2410}
2411
2412bool KCoreDirListerPrivate::doMimeFilter(const QString &mime, const QStringList &filters) const
2413{
2414 if (filters.isEmpty()) {
2415 return true;
2416 }
2417
2418 QMimeDatabase db;
2419 const QMimeType mimeptr = db.mimeTypeForName(mime);
2420 if (!mimeptr.isValid()) {
2421 return false;
2422 }
2423
2424 qCDebug(KIO_CORE_DIRLISTER) << "doMimeFilter: investigating:" << mimeptr.name();
2425 return std::any_of(filters.cbegin(), filters.cend(), [&mimeptr](const QString &filter) {
2426 return mimeptr.inherits(filter);
2427 });
2428}
2429
2430bool KCoreDirListerPrivate::doMimeExcludeFilter(const QString &mime, const QStringList &filters) const
2431{
2432 return !std::any_of(filters.cbegin(), filters.cend(), [&mime](const QString &filter) {
2433 return mime == filter;
2434 });
2435}
2436
2437// ================= private methods ================= //
2438
2439void KCoreDirListerPrivate::addNewItem(const QUrl &directoryUrl, const KFileItem &item)
2440{
2441 if (!isItemVisible(item)) {
2442 return; // No reason to continue... bailing out here prevents a MIME type scan.
2443 }
2444
2445 qCDebug(KIO_CORE_DIRLISTER) << "in" << directoryUrl << "item:" << item.url();
2446
2447 if (matchesMimeFilter(item)) {
2448 Q_ASSERT(!item.isNull());
2449 lstNewItems[directoryUrl].append(item); // items not filtered
2450 } else {
2451 Q_ASSERT(!item.isNull());
2452 lstMimeFilteredItems.append(item); // only filtered by MIME type
2453 }
2454}
2455
2456void KCoreDirListerPrivate::addNewItems(const QUrl &directoryUrl, const QList<KFileItem> &items)
2457{
2458 // TODO: make this faster - test if we have a filter at all first
2459 // DF: was this profiled? The matchesFoo() functions should be fast, w/o filters...
2460 // Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good.
2461 for (const auto &item : items) {
2462 addNewItem(directoryUrl, item);
2463 }
2464}
2465
2466void KCoreDirListerPrivate::addRefreshItem(const QUrl &directoryUrl, const KFileItem &oldItem, const KFileItem &item)
2467{
2468 // Refreshing the root item "." of a dirlister
2469 if (directoryUrl == item.url()) {
2470 lstRefreshItems.append({oldItem, item});
2471 return;
2472 }
2473
2474 const bool refreshItemWasFiltered = !isItemVisible(oldItem) || !matchesMimeFilter(oldItem);
2475 if (item.exists() && isItemVisible(item) && matchesMimeFilter(item)) {
2476 if (refreshItemWasFiltered) {
2477 Q_ASSERT(!item.isNull());
2478 lstNewItems[directoryUrl].append(item);
2479 } else {
2480 Q_ASSERT(!item.isNull());
2481 lstRefreshItems.append(qMakePair(oldItem, item));
2482 }
2483 } else if (!refreshItemWasFiltered) {
2484 // notify the user that the MIME type of a file changed that doesn't match
2485 // a filter or does match an exclude filter
2486 // This also happens when renaming foo to .foo and dot files are hidden (#174721)
2487 Q_ASSERT(!oldItem.isNull());
2488 lstRemoveItems.append(oldItem);
2489 }
2490}
2491
2492void KCoreDirListerPrivate::emitItems()
2493{
2494 if (!lstNewItems.empty()) {
2495 for (auto it = lstNewItems.cbegin(); it != lstNewItems.cend(); ++it) {
2496 const auto &val = it.value();
2497 Q_EMIT q->itemsAdded(it.key(), val);
2498 Q_EMIT q->newItems(val); // compat
2499 }
2500 lstNewItems.clear();
2501 }
2502
2503 if (!lstMimeFilteredItems.empty()) {
2504 Q_EMIT q->itemsFilteredByMime(lstMimeFilteredItems);
2505 lstMimeFilteredItems.clear();
2506 }
2507
2508 if (!lstRefreshItems.empty()) {
2509 Q_EMIT q->refreshItems(lstRefreshItems);
2510 lstRefreshItems.clear();
2511 }
2512
2513 if (!lstRemoveItems.empty()) {
2514 Q_EMIT q->itemsDeleted(lstRemoveItems);
2515 lstRemoveItems.clear();
2516 }
2517}
2518
2519bool KCoreDirListerPrivate::isItemVisible(const KFileItem &item) const
2520{
2521 // Note that this doesn't include MIME type filters, because
2522 // of the itemsFilteredByMime signal. Filtered-by-MIME-type items are
2523 // considered "visible", they are just visible via a different signal...
2524 return (!settings.dirOnlyMode || item.isDir()) && matchesFilter(item);
2525}
2526
2527void KCoreDirListerPrivate::emitItemsDeleted(const KFileItemList &itemsList)
2528{
2529 KFileItemList items;
2530 std::copy_if(itemsList.cbegin(), itemsList.cend(), std::back_inserter(items), [this](const KFileItem &item) {
2531 return isItemVisible(item) || matchesMimeFilter(item);
2532 });
2533 if (!items.isEmpty()) {
2534 Q_EMIT q->itemsDeleted(items);
2535 }
2536}
2537
2538KCoreDirListerPrivate::KCoreDirListerPrivate(KCoreDirLister *qq)
2539 : q(qq)
2540{
2541}
2542
2543// ================ private slots ================ //
2544
2545void KCoreDirListerPrivate::slotInfoMessage(KJob *, const QString &message)
2546{
2547 Q_EMIT q->infoMessage(message);
2548}
2549
2550void KCoreDirListerPrivate::slotPercent(KJob *job, unsigned long pcnt)
2551{
2552 jobData[static_cast<KIO::ListJob *>(job)].percent = pcnt;
2553
2554 int result = 0;
2555
2556 KIO::filesize_t size = 0;
2557
2558 for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) {
2559 const JobData &data = dataIt.value();
2560 result += data.percent * data.totalSize;
2561 size += data.totalSize;
2562 }
2563
2564 if (size != 0) {
2565 result /= size;
2566 } else {
2567 result = 100;
2568 }
2569 Q_EMIT q->percent(result);
2570}
2571
2572void KCoreDirListerPrivate::slotTotalSize(KJob *job, qulonglong size)
2573{
2574 jobData[static_cast<KIO::ListJob *>(job)].totalSize = size;
2575
2576 KIO::filesize_t result = 0;
2577 for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) {
2578 result += dataIt.value().totalSize;
2579 }
2580
2581 Q_EMIT q->totalSize(result);
2582}
2583
2584void KCoreDirListerPrivate::slotProcessedSize(KJob *job, qulonglong size)
2585{
2586 jobData[static_cast<KIO::ListJob *>(job)].processedSize = size;
2587
2588 KIO::filesize_t result = 0;
2589 for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) {
2590 result += dataIt.value().processedSize;
2591 }
2592
2593 Q_EMIT q->processedSize(result);
2594}
2595
2596void KCoreDirListerPrivate::slotSpeed(KJob *job, unsigned long spd)
2597{
2598 jobData[static_cast<KIO::ListJob *>(job)].speed = spd;
2599
2600 int result = 0;
2601 for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) {
2602 result += dataIt.value().speed;
2603 }
2604
2605 Q_EMIT q->speed(result);
2606}
2607
2608uint KCoreDirListerPrivate::numJobs()
2609{
2610#ifdef DEBUG_CACHE
2611 // This code helps detecting stale entries in the jobData map.
2612 qCDebug(KIO_CORE_DIRLISTER) << q << "numJobs:" << jobData.count();
2613 for (auto it = jobData.cbegin(); it != jobData.cend(); ++it) {
2614 qCDebug(KIO_CORE_DIRLISTER) << (void *)it.key();
2615 qCDebug(KIO_CORE_DIRLISTER) << it.key();
2616 }
2617#endif
2618
2619 return jobData.count();
2620}
2621
2622void KCoreDirListerPrivate::jobDone(KIO::ListJob *job)
2623{
2624 jobData.remove(job);
2625}
2626
2628{
2629 KCoreDirListerPrivate::JobData data;
2630 data.speed = 0;
2631 data.percent = 0;
2632 data.processedSize = 0;
2633 data.totalSize = 0;
2634
2635 d->jobData.insert(job, data);
2636 d->complete = false;
2637}
2638
2639void KCoreDirListerPrivate::connectJob(KIO::ListJob *job)
2640{
2641 q->connect(job, &KJob::infoMessage, q, [this](KJob *job, const QString &plain) {
2642 slotInfoMessage(job, plain);
2643 });
2644
2645 q->connect(job, &KJob::percentChanged, q, [this](KJob *job, ulong _percent) {
2646 slotPercent(job, _percent);
2647 });
2648
2649 q->connect(job, &KJob::totalSize, q, [this](KJob *job, qulonglong _size) {
2650 slotTotalSize(job, _size);
2651 });
2652 q->connect(job, &KJob::processedSize, q, [this](KJob *job, qulonglong _psize) {
2653 slotProcessedSize(job, _psize);
2654 });
2655 q->connect(job, &KJob::speed, q, [this](KJob *job, qulonglong _speed) {
2656 slotSpeed(job, _speed);
2657 });
2658}
2659
2661{
2662 return itemsForDir(url(), which);
2663}
2664
2666{
2667 QList<KFileItem> *allItems = s_kDirListerCache.localData().itemsForDir(dir);
2668 KFileItemList result;
2669 if (!allItems) {
2670 return result;
2671 }
2672
2673 if (which == AllItems) {
2674 return KFileItemList(*allItems);
2675 } else { // only items passing the filters
2676 std::copy_if(allItems->cbegin(), allItems->cend(), std::back_inserter(result), [this](const KFileItem &item) {
2677 return d->isItemVisible(item) && d->matchesMimeFilter(item);
2678 });
2679 }
2680 return result;
2681}
2682
2683bool KCoreDirLister::delayedMimeTypes() const
2684{
2685 return d->delayedMimeTypes;
2686}
2687
2688void KCoreDirLister::setDelayedMimeTypes(bool delayedMimeTypes)
2689{
2690 d->delayedMimeTypes = delayedMimeTypes;
2691}
2692
2693// called by KCoreDirListerCache::slotRedirection
2694void KCoreDirListerPrivate::redirect(const QUrl &oldUrl, const QUrl &newUrl, bool keepItems)
2695{
2696 if (url.matches(oldUrl, QUrl::StripTrailingSlash)) {
2697 if (!keepItems) {
2698 rootFileItem = KFileItem();
2699 } else {
2700 rootFileItem.setUrl(newUrl);
2701 }
2702 url = newUrl;
2703 }
2704
2705 const int idx = lstDirs.indexOf(oldUrl);
2706 if (idx == -1) {
2707 qCWarning(KIO_CORE) << "Unexpected redirection from" << oldUrl << "to" << newUrl << "but this dirlister is currently listing/holding" << lstDirs;
2708 } else {
2709 lstDirs[idx] = newUrl;
2710 }
2711
2712 if (lstDirs.count() == 1) {
2713 if (!keepItems) {
2714 Q_EMIT q->clear();
2715 }
2716 } else {
2717 if (!keepItems) {
2718 Q_EMIT q->clearDir(oldUrl);
2719 }
2720 }
2721 Q_EMIT q->redirection(oldUrl, newUrl);
2722}
2723
2724void KCoreDirListerCacheDirectoryData::moveListersWithoutCachedItemsJob(const QUrl &url)
2725{
2726 // Move dirlisters from listersCurrentlyListing to listersCurrentlyHolding,
2727 // but not those that are still waiting on a CachedItemsJob...
2728 // Unit-testing note:
2729 // Run kdirmodeltest in valgrind to hit the case where an update
2730 // is triggered while a lister has a CachedItemsJob (different timing...)
2731 QMutableListIterator<KCoreDirLister *> lister_it(listersCurrentlyListing);
2732 while (lister_it.hasNext()) {
2733 KCoreDirLister *kdl = lister_it.next();
2734 if (!kdl->d->cachedItemsJobForUrl(url)) {
2735 // OK, move this lister from "currently listing" to "currently holding".
2736
2737 // Huh? The KCoreDirLister was present twice in listersCurrentlyListing, or was in both lists?
2738 Q_ASSERT(!listersCurrentlyHolding.contains(kdl));
2739 if (!listersCurrentlyHolding.contains(kdl)) {
2740 listersCurrentlyHolding.append(kdl);
2741 }
2742 lister_it.remove();
2743 } else {
2744 qCDebug(KIO_CORE_DIRLISTER) << "Not moving" << kdl << "to listersCurrentlyHolding because it still has job" << kdl->d->m_cachedItemsJobs;
2745 }
2746 }
2747}
2748
2750{
2751 if (s_kDirListerCache.hasLocalData()) {
2752 return s_kDirListerCache.localData().itemForUrl(url);
2753 } else {
2754 return {};
2755 }
2756}
2757
2758bool KCoreDirLister::autoErrorHandlingEnabled() const
2759{
2760 return d->m_autoErrorHandling;
2761}
2762
2764{
2765 d->m_autoErrorHandling = enable;
2766}
2767
2768KCoreDirListerCache::CacheHiddenFile *KCoreDirListerCache::cachedDotHiddenForDir(const QString &dir)
2769{
2770 const QString path = dir + QLatin1String("/.hidden");
2771 QFile dotHiddenFile(path);
2772
2773 if (dotHiddenFile.exists()) {
2774 const QDateTime mtime = QFileInfo(dotHiddenFile).lastModified();
2775 CacheHiddenFile *cachedDotHiddenFile = m_cacheHiddenFiles.object(path);
2776
2777 if (cachedDotHiddenFile && mtime <= cachedDotHiddenFile->mtime) {
2778 // ".hidden" is in cache and still valid (the file was not modified since then),
2779 // so return it
2780 return cachedDotHiddenFile;
2781 } else {
2782 // read the ".hidden" file, then cache it and return it
2783 if (dotHiddenFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
2784 std::set<QString> filesToHide;
2785 QTextStream stream(&dotHiddenFile);
2786 while (!stream.atEnd()) {
2787 QString name = stream.readLine();
2788 if (!name.isEmpty()) {
2789 filesToHide.insert(name);
2790 }
2791 }
2792
2793 m_cacheHiddenFiles.insert(path, new CacheHiddenFile(mtime, std::move(filesToHide)));
2794
2795 return m_cacheHiddenFiles.object(path);
2796 }
2797 }
2798 }
2799
2800 return {};
2801}
2802
2803#include "moc_kcoredirlister.cpp"
2804#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 Fri May 3 2024 11:49:40 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.