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