1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2002-2006 Michael Brade <brade@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#ifndef KCOREDIRLISTER_P_H
9#define KCOREDIRLISTER_P_H
10
11#include "kfileitem.h"
12
13#ifndef KIO_ANDROID_STUB
14#include "kdirnotify.h"
15#endif
16
17#include <QCache>
18#include <QCoreApplication>
19#include <QFileInfo>
20#include <QHash>
21#include <QList>
22#include <QMap>
23#include <QTimer>
24#include <QUrl>
25
26#include <KDirWatch>
27#include <kio/global.h>
28
29#include <set>
30
31class QRegularExpression;
32class KCoreDirLister;
33namespace KIO
34{
35class Job;
36class ListJob;
37}
38class OrgKdeKDirNotifyInterface;
39struct KCoreDirListerCacheDirectoryData;
40
41class KCoreDirListerPrivate
42{
43public:
44 explicit KCoreDirListerPrivate(KCoreDirLister *qq);
45
46 void emitCachedItems(const QUrl &, bool, bool);
47 void slotInfoMessage(KJob *, const QString &);
48 void slotPercent(KJob *, unsigned long);
49 void slotTotalSize(KJob *, qulonglong);
50 void slotProcessedSize(KJob *, qulonglong);
51 void slotSpeed(KJob *, unsigned long);
52
53 /*
54 * Called by the public matchesMimeFilter() to do the
55 * actual filtering. Those methods may be reimplemented to customize
56 * filtering.
57 * @param mimeType the MIME type to filter
58 * @param filters the list of MIME types to filter
59 */
60 bool doMimeFilter(const QString &mimeType, const QStringList &filters) const;
61 bool doMimeExcludeFilter(const QString &mimeExclude, const QStringList &filters) const;
62 void connectJob(KIO::ListJob *);
63 void jobDone(KIO::ListJob *);
64 uint numJobs();
65 void addNewItem(const QUrl &directoryUrl, const KFileItem &item);
66 void addNewItems(const QUrl &directoryUrl, const QList<KFileItem> &items);
67 void addRefreshItem(const QUrl &directoryUrl, const KFileItem &oldItem, const KFileItem &item);
68 void emitItems();
69 void emitItemsDeleted(const KFileItemList &items);
70
71 /*
72 * Called for every new item before emitting newItems().
73 * You may reimplement this method in a subclass to implement your own
74 * filtering.
75 * The default implementation filters out ".." and everything not matching
76 * the name filter(s)
77 * @return @c true if the item is "ok".
78 * @c false if the item shall not be shown in a view, e.g.
79 * files not matching a pattern *.cpp ( KFileItem::isHidden())
80 * @see matchesFilter
81 * @see setNameFilter
82 */
83 bool matchesFilter(const KFileItem &) const;
84
85 /*
86 * Called for every new item before emitting newItems().
87 * You may reimplement this method in a subclass to implement your own
88 * filtering.
89 * The default implementation filters out everything not matching
90 * the mime filter(s)
91 * @return @c true if the item is "ok".
92 * @c false if the item shall not be shown in a view, e.g.
93 * files not matching the mime filter
94 * @see matchesMimeFilter
95 * @see setMimeFilter
96 */
97 bool matchesMimeFilter(const KFileItem &) const;
98
99 /**
100 * Redirect this dirlister from oldUrl to newUrl.
101 * @param keepItems if true, keep the fileitems (e.g. when renaming an existing dir);
102 * if false, clear out everything (e.g. when redirecting during listing).
103 */
104 void redirect(const QUrl &oldUrl, const QUrl &newUrl, bool keepItems);
105
106 /**
107 * Should this item be visible according to the current filter settings?
108 */
109 bool isItemVisible(const KFileItem &item) const;
110
111 void prepareForSettingsChange()
112 {
113 if (!hasPendingChanges) {
114 hasPendingChanges = true;
115 oldSettings = settings;
116 }
117 }
118
119 void emitChanges();
120
121 class CachedItemsJob;
122 CachedItemsJob *cachedItemsJobForUrl(const QUrl &url) const;
123
124 KCoreDirLister *const q;
125
126 /**
127 * List of dirs handled by this dirlister. The first entry is the base URL.
128 * For a tree view, it contains all the dirs shown.
129 */
130 QList<QUrl> lstDirs;
131
132 // toplevel URL
133 QUrl url;
134
135 bool complete = false;
136 bool autoUpdate = false;
137 bool delayedMimeTypes = false;
138 bool hasPendingChanges = false; // i.e. settings != oldSettings
139 bool m_autoErrorHandling = true;
140 bool requestMimeTypeWhileListing = false;
141
142 struct JobData {
143 long unsigned int percent, speed;
144 KIO::filesize_t processedSize, totalSize;
145 };
146
147 QMap<KIO::ListJob *, JobData> jobData;
148
149 // file item for the root itself (".")
150 KFileItem rootFileItem;
151
152 typedef QHash<QUrl, KFileItemList> NewItemsHash;
153 NewItemsHash lstNewItems;
154 QList<QPair<KFileItem, KFileItem>> lstRefreshItems;
155 KFileItemList lstMimeFilteredItems, lstRemoveItems;
156
157 QList<CachedItemsJob *> m_cachedItemsJobs;
158
159 QString nameFilter; // parsed into lstFilters
160
161 struct FilterSettings {
162 FilterSettings()
163 : isShowingDotFiles(false)
164 , dirOnlyMode(false)
165 {
166 }
167 bool isShowingDotFiles;
168 bool dirOnlyMode;
169 QList<QRegularExpression> lstFilters;
170 QStringList mimeFilter;
171 QStringList mimeExcludeFilter;
172 };
173 FilterSettings settings;
174 FilterSettings oldSettings;
175
176 friend class KCoreDirListerCache;
177};
178
179/**
180 * Design of the cache:
181 * There is a single KCoreDirListerCache for the whole process.
182 * It holds all the items used by the dir listers (itemsInUse)
183 * as well as a cache of the recently used items (itemsCached).
184 * Those items are grouped by directory (a DirItem represents a whole directory).
185 *
186 * KCoreDirListerCache also runs all the jobs for listing directories, whether they are for
187 * normal listing or for updates.
188 * For faster lookups, it also stores a hash table, which gives for a directory URL:
189 * - the dirlisters holding that URL (listersCurrentlyHolding)
190 * - the dirlisters currently listing that URL (listersCurrentlyListing)
191 */
192class KCoreDirListerCache : public QObject
193{
194 Q_OBJECT
195public:
196 KCoreDirListerCache(); // only called by QThreadStorage internally
197 ~KCoreDirListerCache() override;
198
199 void updateDirectory(const QUrl &dir);
200
201 KFileItem itemForUrl(const QUrl &url) const;
202 QList<KFileItem> *itemsForDir(const QUrl &dir) const;
203
204 bool listDir(KCoreDirLister *lister, const QUrl &_url, bool _keep, bool _reload);
205
206 // stop all running jobs for lister
207 void stop(KCoreDirLister *lister, bool silent = false);
208 // stop just the job listing url for lister
209 void stopListingUrl(KCoreDirLister *lister, const QUrl &_url, bool silent = false);
210
211 void setAutoUpdate(KCoreDirLister *lister, bool enable);
212
213 void forgetDirs(KCoreDirLister *lister);
214 void forgetDirs(KCoreDirLister *lister, const QUrl &_url, bool notify);
215
216 KFileItem findByName(const KCoreDirLister *lister, const QString &_name) const;
217 // findByUrl returns a pointer so that it's possible to modify the item.
218 // See itemForUrl for the version that returns a readonly kfileitem.
219 // @param lister can be 0. If set, it is checked that the url is held by the lister
220 KFileItem findByUrl(const KCoreDirLister *lister, const QUrl &url) const;
221
222 // Called by CachedItemsJob:
223 // Emits the cached items, for this lister and this url
224 void emitItemsFromCache(KCoreDirListerPrivate::CachedItemsJob *job, KCoreDirLister *lister, const QUrl &_url, bool _reload, bool _emitCompleted);
225 // Called by CachedItemsJob:
226 void forgetCachedItemsJob(KCoreDirListerPrivate::CachedItemsJob *job, KCoreDirLister *lister, const QUrl &url);
227
228public Q_SLOTS:
229 /**
230 * Notify that files have been added in @p directory
231 * The receiver will list that directory again to find
232 * the new items (since it needs more than just the names anyway).
233 * Connected to the DBus signal from the KDirNotify interface.
234 */
235 void slotFilesAdded(const QString &urlDirectory);
236
237 /**
238 * Notify that files have been deleted.
239 * This call passes the exact urls of the deleted files
240 * so that any view showing them can simply remove them
241 * or be closed (if its current dir was deleted)
242 * Connected to the DBus signal from the KDirNotify interface.
243 */
244 void slotFilesRemoved(const QStringList &fileList);
245
246 /**
247 * Notify that files have been changed.
248 * At the moment, this is only used for new icon, but it could be
249 * used for size etc. as well.
250 * Connected to the DBus signal from the KDirNotify interface.
251 */
252 void slotFilesChanged(const QStringList &fileList);
253 void slotFileRenamed(const QString &srcUrl, const QString &dstUrl, const QString &dstPath);
254
255private Q_SLOTS:
256 void slotFileDirty(const QString &_file);
257 void slotFileCreated(const QString &_file);
258 void slotFileDeleted(const QString &_file);
259
260 void slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries);
261 void slotResult(KJob *j);
262 void slotRedirection(KIO::Job *job, const QUrl &url);
263
264 void slotUpdateEntries(KIO::Job *job, const KIO::UDSEntryList &entries);
265 void slotUpdateResult(KJob *job);
266 void processPendingUpdates();
267
268private:
269 void itemsAddedInDirectory(const QUrl &url);
270
271 class DirItem;
272 DirItem *dirItemForUrl(const QUrl &dir) const;
273
274 void stopListJob(const QUrl &url, bool silent);
275
276 KIO::ListJob *jobForUrl(const QUrl &url, KIO::ListJob *not_job = nullptr);
277 const QUrl &joburl(KIO::ListJob *job);
278
279 void killJob(KIO::ListJob *job);
280
281 // Called when something tells us that the directory @p url has changed.
282 // Returns true if @p url is held by some lister (meaning: do the update now)
283 // otherwise mark the cached item as not-up-to-date for later and return false
284 bool checkUpdate(const QUrl &url);
285
286 // Helper method for slotFileDirty
287 void handleFileDirty(const QUrl &url);
288 void handleDirDirty(const QUrl &url);
289
290 // when there were items deleted from the filesystem all the listers holding
291 // the parent directory need to be notified, the items have to be deleted
292 // and removed from the cache including all the children.
293 void deleteUnmarkedItems(const QList<KCoreDirLister *> &, QList<KFileItem> &lstItems, const QHash<QString, KFileItem> &itemsToDelete);
294
295 // Helper method called when we know that a list of items was deleted
296 void itemsDeleted(const QList<KCoreDirLister *> &listers, const KFileItemList &deletedItems);
297 void slotFilesRemoved(const QList<QUrl> &urls);
298 // common for slotRedirection and slotFileRenamed
299 void renameDir(const QUrl &oldUrl, const QUrl &url);
300 // common for deleteUnmarkedItems and slotFilesRemoved
301 void deleteDir(const QUrl &dirUrl);
302 // remove directory from cache (itemsCached), including all child dirs
303 void removeDirFromCache(const QUrl &dir);
304 // helper for renameDir
305 void emitRedirections(const QUrl &oldUrl, const QUrl &url);
306
307 /**
308 * Emits refreshItem() in the directories that cared for oldItem.
309 * The caller has to remember to call emitItems in the set of dirlisters returned
310 * (but this allows to buffer change notifications)
311 */
312 std::set<KCoreDirLister *> emitRefreshItem(const KFileItem &oldItem, const KFileItem &fileitem);
313
314 /**
315 * Remove the item from the sorted by url list matching @p oldUrl,
316 * that is in the wrong place (because its url has changed) and insert @p item in the right place.
317 * @param oldUrl the previous url of the @p item
318 * @param item the modified item to be inserted
319 */
320 void reinsert(const KFileItem &item, const QUrl &oldUrl)
321 {
322 const QUrl parentDir = oldUrl.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash);
323 DirItem *dirItem = dirItemForUrl(dir: parentDir);
324 if (dirItem) {
325 auto it = std::lower_bound(first: dirItem->lstItems.begin(), last: dirItem->lstItems.end(), val: oldUrl);
326 Q_ASSERT(it != dirItem->lstItems.end());
327 dirItem->lstItems.erase(pos: it);
328 dirItem->insert(item);
329 }
330 }
331
332 void remove(const QUrl &oldUrl)
333 {
334 const QUrl parentDir = oldUrl.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash);
335 DirItem *dirItem = dirItemForUrl(dir: parentDir);
336 if (dirItem) {
337 auto it = std::lower_bound(first: dirItem->lstItems.begin(), last: dirItem->lstItems.end(), val: oldUrl);
338 Q_ASSERT(it != dirItem->lstItems.end());
339 dirItem->lstItems.erase(pos: it);
340 }
341 }
342
343 /**
344 * When KDirWatch tells us that something changed in "dir", we need to
345 * also notify the dirlisters that are listing a symlink to "dir" (#213799)
346 */
347 QList<QUrl> directoriesForCanonicalPath(const QUrl &dir) const;
348
349 // Definition of the cache of ".hidden" files
350 struct CacheHiddenFile {
351 CacheHiddenFile(const QDateTime &mtime, std::set<QString> &&listedFilesParam)
352 : mtime(mtime)
353 , listedFiles(std::move(listedFilesParam))
354 {
355 }
356 QDateTime mtime;
357 std::set<QString> listedFiles;
358 };
359
360 /**
361 * Returns the names listed in dir's ".hidden" file, if it exists.
362 * If a file named ".hidden" exists in the @p dir directory, this method
363 * returns all the file names listed in that file. If it doesn't exist, an
364 * empty set is returned.
365 * @param dir path to the target directory.
366 * @return names listed in the directory's ".hidden" file (empty if it doesn't exist).
367 */
368 CacheHiddenFile *cachedDotHiddenForDir(const QString &dir);
369
370#ifndef NDEBUG
371 void printDebug();
372#endif
373
374 class DirItem
375 {
376 public:
377 DirItem(const QUrl &dir, const QString &canonicalPath)
378 : url(dir)
379 , m_canonicalPath(canonicalPath)
380 {
381 autoUpdates = 0;
382 complete = false;
383 watchedWhileInCache = false;
384 }
385
386 ~DirItem()
387 {
388 if (autoUpdates) {
389 if (KDirWatch::exists() && url.isLocalFile()) {
390 KDirWatch::self()->removeDir(path: m_canonicalPath);
391 }
392 // Since sendSignal goes through D-Bus, QCoreApplication has to be available
393 // which might not be the case anymore from a global static dtor like the
394 // lister cache
395 if (QCoreApplication::instance()) {
396 sendSignal(entering: false, url);
397 }
398 }
399 lstItems.clear();
400 }
401
402 DirItem(const DirItem &) = delete;
403 DirItem &operator=(const DirItem &) = delete;
404
405 void sendSignal(bool entering, const QUrl &url)
406 {
407 // Note that "entering" means "start watching", and "leaving" means "stop watching"
408 // (i.e. it's not when the user leaves the directory, it's when the directory is removed from the cache)
409#ifndef KIO_ANDROID_STUB
410 if (entering) {
411 org::kde::KDirNotify::emitEnteredDirectory(url);
412 } else {
413 org::kde::KDirNotify::emitLeftDirectory(url);
414 }
415#endif
416 }
417
418 void redirect(const QUrl &newUrl)
419 {
420 if (autoUpdates) {
421 if (url.isLocalFile()) {
422 KDirWatch::self()->removeDir(path: m_canonicalPath);
423 }
424 sendSignal(entering: false, url);
425
426 if (newUrl.isLocalFile()) {
427 m_canonicalPath = QFileInfo(newUrl.toLocalFile()).canonicalFilePath();
428 KDirWatch::self()->addDir(path: m_canonicalPath);
429 }
430 sendSignal(entering: true, url: newUrl);
431 }
432
433 url = newUrl;
434
435 if (!rootItem.isNull()) {
436 rootItem.setUrl(newUrl);
437 }
438 }
439
440 void incAutoUpdate()
441 {
442 if (autoUpdates++ == 0) {
443 if (url.isLocalFile()) {
444 KDirWatch::self()->addDir(path: m_canonicalPath);
445 }
446 sendSignal(entering: true, url);
447 }
448 }
449
450 void decAutoUpdate()
451 {
452 if (--autoUpdates == 0) {
453 if (url.isLocalFile()) {
454 KDirWatch::self()->removeDir(path: m_canonicalPath);
455 }
456 sendSignal(entering: false, url);
457 }
458
459 else if (autoUpdates < 0) {
460 autoUpdates = 0;
461 }
462 }
463
464 // Insert the item in the sorted list
465 void insert(const KFileItem &item)
466 {
467 auto it = std::lower_bound(first: lstItems.begin(), last: lstItems.end(), val: item.url());
468 lstItems.insert(before: it, t: item);
469 }
470
471 // Insert the already sorted items in the sorted list
472 void insertSortedItems(const KFileItemList &items)
473 {
474 if (items.isEmpty()) {
475 return;
476 }
477 lstItems.reserve(asize: lstItems.size() + items.size());
478 auto it = lstItems.begin();
479 for (const auto &item : items) {
480 it = std::lower_bound(first: it, last: lstItems.end(), val: item.url());
481 it = lstItems.insert(before: it, t: item);
482 }
483 }
484
485 // number of KCoreDirListers using autoUpdate for this dir
486 short autoUpdates;
487
488 // this directory is up-to-date
489 bool complete;
490
491 // the directory is watched while being in the cache (useful for proper incAutoUpdate/decAutoUpdate count)
492 bool watchedWhileInCache;
493
494 // the complete url of this directory
495 QUrl url;
496
497 // the local path, with symlinks resolved, so that KDirWatch works
498 QString m_canonicalPath;
499
500 // KFileItem representing the root of this directory.
501 // Remember that this is optional. FTP sites don't return '.' in
502 // the list, so they give no root item
503 KFileItem rootItem;
504 QList<KFileItem> lstItems;
505 };
506
507 QMap<KIO::ListJob *, KIO::UDSEntryList> runningListJobs;
508
509 // an item is a complete directory
510 QHash<QUrl, DirItem *> itemsInUse;
511 QCache<QUrl, DirItem> itemsCached;
512
513 // cache of ".hidden" files
514 QCache<QString /*dot hidden file*/, CacheHiddenFile> m_cacheHiddenFiles;
515
516 typedef QHash<QUrl, KCoreDirListerCacheDirectoryData> DirectoryDataHash;
517 DirectoryDataHash directoryData;
518
519 // Symlink-to-directories are registered here so that we can
520 // find the url that changed, when kdirwatch tells us about
521 // changes in the canonical url. (#213799)
522 QHash<QUrl /*canonical path*/, QList<QUrl> /*dirlister urls*/> canonicalUrls;
523
524 // Set of local files that we have changed recently (according to KDirWatch)
525 // We temporize the notifications by keeping them 500ms in this list.
526 std::set<QString /*path*/> pendingUpdates;
527 std::set<QString /*path*/> pendingDirectoryUpdates;
528 // The timer for doing the delayed updates
529 QTimer pendingUpdateTimer;
530
531 // Set of remote files that have changed recently -- but we can't emit those
532 // changes yet, we need to wait for the "update" directory listing.
533 // The cmp() call can't differ MIME types since they are determined on demand,
534 // this is why we need to remember those files here.
535 std::set<KFileItem> pendingRemoteUpdates;
536
537 // the KDirNotify signals
538 OrgKdeKDirNotifyInterface *kdirnotify;
539
540 struct ItemInUseChange;
541};
542
543// Data associated with a directory url
544// This could be in DirItem but only in the itemsInUse dict...
545struct KCoreDirListerCacheDirectoryData {
546 // A lister can be EITHER in listersCurrentlyListing OR listersCurrentlyHolding
547 // but NOT in both at the same time.
548 // But both lists can have different listers at the same time; this
549 // happens if more listers are requesting url at the same time and
550 // one lister was stopped during the listing of files.
551
552 // Listers that are currently listing this url
553 QList<KCoreDirLister *> listersCurrentlyListing;
554 // Listers that are currently holding this url
555 QList<KCoreDirLister *> listersCurrentlyHolding;
556
557 void moveListersWithoutCachedItemsJob(const QUrl &url);
558};
559
560// This job tells KCoreDirListerCache to emit cached items asynchronously from listDir()
561// to give the KCoreDirLister user enough time for connecting to its signals, and so
562// that KCoreDirListerCache behaves just like when a real KIO::Job is used: nothing
563// is emitted during the openUrl call itself.
564class KCoreDirListerPrivate::CachedItemsJob : public KJob
565{
566 Q_OBJECT
567public:
568 CachedItemsJob(KCoreDirLister *lister, const QUrl &url, bool reload);
569
570 /*reimp*/ void start() override
571 {
572 QMetaObject::invokeMethod(object: this, function: &KCoreDirListerPrivate::CachedItemsJob::done, type: Qt::QueuedConnection);
573 }
574
575 // For updateDirectory() to cancel m_emitCompleted;
576 void setEmitCompleted(bool b)
577 {
578 m_emitCompleted = b;
579 }
580
581 QUrl url() const
582 {
583 return m_url;
584 }
585
586protected:
587 bool doKill() override;
588
589public Q_SLOTS:
590 void done();
591
592private:
593 KCoreDirLister *m_lister;
594 QUrl m_url;
595 bool m_reload;
596 bool m_emitCompleted;
597};
598
599#endif
600

source code of kio/src/core/kcoredirlister_p.h