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