KIO

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

KDE's Doxygen guidelines are available online.