File indexing completed on 2024-04-21 03:55:06

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2002-2006 Michael Brade <brade@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #ifndef KCOREDIRLISTER_P_H
0009 #define KCOREDIRLISTER_P_H
0010 
0011 #include "kfileitem.h"
0012 
0013 #ifndef KIO_ANDROID_STUB
0014 #include "kdirnotify.h"
0015 #endif
0016 
0017 #include <QCache>
0018 #include <QCoreApplication>
0019 #include <QFileInfo>
0020 #include <QHash>
0021 #include <QList>
0022 #include <QMap>
0023 #include <QTimer>
0024 #include <QUrl>
0025 
0026 #include <KDirWatch>
0027 #include <kio/global.h>
0028 
0029 #include <set>
0030 
0031 class QRegularExpression;
0032 class KCoreDirLister;
0033 namespace KIO
0034 {
0035 class Job;
0036 class ListJob;
0037 }
0038 class OrgKdeKDirNotifyInterface;
0039 struct KCoreDirListerCacheDirectoryData;
0040 
0041 class KCoreDirListerPrivate
0042 {
0043 public:
0044     explicit KCoreDirListerPrivate(KCoreDirLister *qq);
0045 
0046     void emitCachedItems(const QUrl &, bool, bool);
0047     void slotInfoMessage(KJob *, const QString &);
0048     void slotPercent(KJob *, unsigned long);
0049     void slotTotalSize(KJob *, qulonglong);
0050     void slotProcessedSize(KJob *, qulonglong);
0051     void slotSpeed(KJob *, unsigned long);
0052 
0053     /*
0054      * Called by the public matchesMimeFilter() to do the
0055      * actual filtering. Those methods may be reimplemented to customize
0056      * filtering.
0057      * @param mimeType the MIME type to filter
0058      * @param filters the list of MIME types to filter
0059      */
0060     bool doMimeFilter(const QString &mimeType, const QStringList &filters) const;
0061     bool doMimeExcludeFilter(const QString &mimeExclude, const QStringList &filters) const;
0062     void connectJob(KIO::ListJob *);
0063     void jobDone(KIO::ListJob *);
0064     uint numJobs();
0065     void addNewItem(const QUrl &directoryUrl, const KFileItem &item);
0066     void addNewItems(const QUrl &directoryUrl, const QList<KFileItem> &items);
0067     void addRefreshItem(const QUrl &directoryUrl, const KFileItem &oldItem, const KFileItem &item);
0068     void emitItems();
0069     void emitItemsDeleted(const KFileItemList &items);
0070 
0071     /*
0072      * Called for every new item before emitting newItems().
0073      * You may reimplement this method in a subclass to implement your own
0074      * filtering.
0075      * The default implementation filters out ".." and everything not matching
0076      * the name filter(s)
0077      * @return @c true if the item is "ok".
0078      *         @c false if the item shall not be shown in a view, e.g.
0079      * files not matching a pattern *.cpp ( KFileItem::isHidden())
0080      * @see matchesFilter
0081      * @see setNameFilter
0082      */
0083     bool matchesFilter(const KFileItem &) const;
0084 
0085     /*
0086      * Called for every new item before emitting newItems().
0087      * You may reimplement this method in a subclass to implement your own
0088      * filtering.
0089      * The default implementation filters out everything not matching
0090      * the mime filter(s)
0091      * @return @c true if the item is "ok".
0092      *         @c false if the item shall not be shown in a view, e.g.
0093      * files not matching the mime filter
0094      * @see matchesMimeFilter
0095      * @see setMimeFilter
0096      */
0097     bool matchesMimeFilter(const KFileItem &) const;
0098 
0099     /**
0100      * Redirect this dirlister from oldUrl to newUrl.
0101      * @param keepItems if true, keep the fileitems (e.g. when renaming an existing dir);
0102      * if false, clear out everything (e.g. when redirecting during listing).
0103      */
0104     void redirect(const QUrl &oldUrl, const QUrl &newUrl, bool keepItems);
0105 
0106     /**
0107      * Should this item be visible according to the current filter settings?
0108      */
0109     bool isItemVisible(const KFileItem &item) const;
0110 
0111     void prepareForSettingsChange()
0112     {
0113         if (!hasPendingChanges) {
0114             hasPendingChanges = true;
0115             oldSettings = settings;
0116         }
0117     }
0118 
0119     void emitChanges();
0120 
0121     class CachedItemsJob;
0122     CachedItemsJob *cachedItemsJobForUrl(const QUrl &url) const;
0123 
0124     KCoreDirLister *const q;
0125 
0126     /**
0127      * List of dirs handled by this dirlister. The first entry is the base URL.
0128      * For a tree view, it contains all the dirs shown.
0129      */
0130     QList<QUrl> lstDirs;
0131 
0132     // toplevel URL
0133     QUrl url;
0134 
0135     bool complete = false;
0136     bool autoUpdate = false;
0137     bool delayedMimeTypes = false;
0138     bool hasPendingChanges = false; // i.e. settings != oldSettings
0139     bool m_autoErrorHandling = true;
0140     bool requestMimeTypeWhileListing = false;
0141 
0142     struct JobData {
0143         long unsigned int percent, speed;
0144         KIO::filesize_t processedSize, totalSize;
0145     };
0146 
0147     QMap<KIO::ListJob *, JobData> jobData;
0148 
0149     // file item for the root itself (".")
0150     KFileItem rootFileItem;
0151 
0152     typedef QHash<QUrl, KFileItemList> NewItemsHash;
0153     NewItemsHash lstNewItems;
0154     QList<QPair<KFileItem, KFileItem>> lstRefreshItems;
0155     KFileItemList lstMimeFilteredItems, lstRemoveItems;
0156 
0157     QList<CachedItemsJob *> m_cachedItemsJobs;
0158 
0159     QString nameFilter; // parsed into lstFilters
0160 
0161     struct FilterSettings {
0162         FilterSettings()
0163             : isShowingDotFiles(false)
0164             , dirOnlyMode(false)
0165         {
0166         }
0167         bool isShowingDotFiles;
0168         bool dirOnlyMode;
0169         QList<QRegularExpression> lstFilters;
0170         QStringList mimeFilter;
0171         QStringList mimeExcludeFilter;
0172     };
0173     FilterSettings settings;
0174     FilterSettings oldSettings;
0175 
0176     friend class KCoreDirListerCache;
0177 };
0178 
0179 /**
0180  * Design of the cache:
0181  * There is a single KCoreDirListerCache for the whole process.
0182  * It holds all the items used by the dir listers (itemsInUse)
0183  * as well as a cache of the recently used items (itemsCached).
0184  * Those items are grouped by directory (a DirItem represents a whole directory).
0185  *
0186  * KCoreDirListerCache also runs all the jobs for listing directories, whether they are for
0187  * normal listing or for updates.
0188  * For faster lookups, it also stores a hash table, which gives for a directory URL:
0189  * - the dirlisters holding that URL (listersCurrentlyHolding)
0190  * - the dirlisters currently listing that URL (listersCurrentlyListing)
0191  */
0192 class KCoreDirListerCache : public QObject
0193 {
0194     Q_OBJECT
0195 public:
0196     KCoreDirListerCache(); // only called by QThreadStorage internally
0197     ~KCoreDirListerCache() override;
0198 
0199     void updateDirectory(const QUrl &dir);
0200 
0201     KFileItem itemForUrl(const QUrl &url) const;
0202     QList<KFileItem> *itemsForDir(const QUrl &dir) const;
0203 
0204     bool listDir(KCoreDirLister *lister, const QUrl &_url, bool _keep, bool _reload);
0205 
0206     // stop all running jobs for lister
0207     void stop(KCoreDirLister *lister, bool silent = false);
0208     // stop just the job listing url for lister
0209     void stopListingUrl(KCoreDirLister *lister, const QUrl &_url, bool silent = false);
0210 
0211     void setAutoUpdate(KCoreDirLister *lister, bool enable);
0212 
0213     void forgetDirs(KCoreDirLister *lister);
0214     void forgetDirs(KCoreDirLister *lister, const QUrl &_url, bool notify);
0215 
0216     KFileItem findByName(const KCoreDirLister *lister, const QString &_name) const;
0217     // findByUrl returns a pointer so that it's possible to modify the item.
0218     // See itemForUrl for the version that returns a readonly kfileitem.
0219     // @param lister can be 0. If set, it is checked that the url is held by the lister
0220     KFileItem findByUrl(const KCoreDirLister *lister, const QUrl &url) const;
0221 
0222     // Called by CachedItemsJob:
0223     // Emits the cached items, for this lister and this url
0224     void emitItemsFromCache(KCoreDirListerPrivate::CachedItemsJob *job, KCoreDirLister *lister, const QUrl &_url, bool _reload, bool _emitCompleted);
0225     // Called by CachedItemsJob:
0226     void forgetCachedItemsJob(KCoreDirListerPrivate::CachedItemsJob *job, KCoreDirLister *lister, const QUrl &url);
0227 
0228 public Q_SLOTS:
0229     /**
0230      * Notify that files have been added in @p directory
0231      * The receiver will list that directory again to find
0232      * the new items (since it needs more than just the names anyway).
0233      * Connected to the DBus signal from the KDirNotify interface.
0234      */
0235     void slotFilesAdded(const QString &urlDirectory);
0236 
0237     /**
0238      * Notify that files have been deleted.
0239      * This call passes the exact urls of the deleted files
0240      * so that any view showing them can simply remove them
0241      * or be closed (if its current dir was deleted)
0242      * Connected to the DBus signal from the KDirNotify interface.
0243      */
0244     void slotFilesRemoved(const QStringList &fileList);
0245 
0246     /**
0247      * Notify that files have been changed.
0248      * At the moment, this is only used for new icon, but it could be
0249      * used for size etc. as well.
0250      * Connected to the DBus signal from the KDirNotify interface.
0251      */
0252     void slotFilesChanged(const QStringList &fileList);
0253     void slotFileRenamed(const QString &srcUrl, const QString &dstUrl, const QString &dstPath);
0254 
0255 private Q_SLOTS:
0256     void slotFileDirty(const QString &_file);
0257     void slotFileCreated(const QString &_file);
0258     void slotFileDeleted(const QString &_file);
0259 
0260     void slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries);
0261     void slotResult(KJob *j);
0262     void slotRedirection(KIO::Job *job, const QUrl &url);
0263 
0264     void slotUpdateEntries(KIO::Job *job, const KIO::UDSEntryList &entries);
0265     void slotUpdateResult(KJob *job);
0266     void processPendingUpdates();
0267 
0268 private:
0269     void itemsAddedInDirectory(const QUrl &url);
0270 
0271     class DirItem;
0272     DirItem *dirItemForUrl(const QUrl &dir) const;
0273 
0274     void stopListJob(const QUrl &url, bool silent);
0275 
0276     KIO::ListJob *jobForUrl(const QUrl &url, KIO::ListJob *not_job = nullptr);
0277     const QUrl &joburl(KIO::ListJob *job);
0278 
0279     void killJob(KIO::ListJob *job);
0280 
0281     // Called when something tells us that the directory @p url has changed.
0282     // Returns true if @p url is held by some lister (meaning: do the update now)
0283     // otherwise mark the cached item as not-up-to-date for later and return false
0284     bool checkUpdate(const QUrl &url);
0285 
0286     // Helper method for slotFileDirty
0287     void handleFileDirty(const QUrl &url);
0288     void handleDirDirty(const QUrl &url);
0289 
0290     // when there were items deleted from the filesystem all the listers holding
0291     // the parent directory need to be notified, the items have to be deleted
0292     // and removed from the cache including all the children.
0293     void deleteUnmarkedItems(const QList<KCoreDirLister *> &, QList<KFileItem> &lstItems, const QHash<QString, KFileItem> &itemsToDelete);
0294 
0295     // Helper method called when we know that a list of items was deleted
0296     void itemsDeleted(const QList<KCoreDirLister *> &listers, const KFileItemList &deletedItems);
0297     void slotFilesRemoved(const QList<QUrl> &urls);
0298     // common for slotRedirection and slotFileRenamed
0299     void renameDir(const QUrl &oldUrl, const QUrl &url);
0300     // common for deleteUnmarkedItems and slotFilesRemoved
0301     void deleteDir(const QUrl &dirUrl);
0302     // remove directory from cache (itemsCached), including all child dirs
0303     void removeDirFromCache(const QUrl &dir);
0304     // helper for renameDir
0305     void emitRedirections(const QUrl &oldUrl, const QUrl &url);
0306 
0307     /**
0308      * Emits refreshItem() in the directories that cared for oldItem.
0309      * The caller has to remember to call emitItems in the set of dirlisters returned
0310      * (but this allows to buffer change notifications)
0311      */
0312     std::set<KCoreDirLister *> emitRefreshItem(const KFileItem &oldItem, const KFileItem &fileitem);
0313 
0314     /**
0315      * Remove the item from the sorted by url list matching @p oldUrl,
0316      * that is in the wrong place (because its url has changed) and insert @p item in the right place.
0317      * @param oldUrl the previous url of the @p item
0318      * @param item the modified item to be inserted
0319      */
0320     void reinsert(const KFileItem &item, const QUrl &oldUrl)
0321     {
0322         const QUrl parentDir = oldUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
0323         DirItem *dirItem = dirItemForUrl(parentDir);
0324         if (dirItem) {
0325             auto it = std::lower_bound(dirItem->lstItems.begin(), dirItem->lstItems.end(), oldUrl);
0326             Q_ASSERT(it != dirItem->lstItems.end());
0327             dirItem->lstItems.erase(it);
0328             dirItem->insert(item);
0329         }
0330     }
0331 
0332     void remove(const QUrl &oldUrl)
0333     {
0334         const QUrl parentDir = oldUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
0335         DirItem *dirItem = dirItemForUrl(parentDir);
0336         if (dirItem) {
0337             auto it = std::lower_bound(dirItem->lstItems.begin(), dirItem->lstItems.end(), oldUrl);
0338             Q_ASSERT(it != dirItem->lstItems.end());
0339             dirItem->lstItems.erase(it);
0340         }
0341     }
0342 
0343     /**
0344      * When KDirWatch tells us that something changed in "dir", we need to
0345      * also notify the dirlisters that are listing a symlink to "dir" (#213799)
0346      */
0347     QList<QUrl> directoriesForCanonicalPath(const QUrl &dir) const;
0348 
0349     // Definition of the cache of ".hidden" files
0350     struct CacheHiddenFile {
0351         CacheHiddenFile(const QDateTime &mtime, std::set<QString> &&listedFilesParam)
0352             : mtime(mtime)
0353             , listedFiles(std::move(listedFilesParam))
0354         {
0355         }
0356         QDateTime mtime;
0357         std::set<QString> listedFiles;
0358     };
0359 
0360     /**
0361      * Returns the names listed in dir's ".hidden" file, if it exists.
0362      * If a file named ".hidden" exists in the @p dir directory, this method
0363      * returns all the file names listed in that file. If it doesn't exist, an
0364      * empty set is returned.
0365      * @param dir path to the target directory.
0366      * @return names listed in the directory's ".hidden" file (empty if it doesn't exist).
0367      */
0368     CacheHiddenFile *cachedDotHiddenForDir(const QString &dir);
0369 
0370 #ifndef NDEBUG
0371     void printDebug();
0372 #endif
0373 
0374     class DirItem
0375     {
0376     public:
0377         DirItem(const QUrl &dir, const QString &canonicalPath)
0378             : url(dir)
0379             , m_canonicalPath(canonicalPath)
0380         {
0381             autoUpdates = 0;
0382             complete = false;
0383             watchedWhileInCache = false;
0384         }
0385 
0386         ~DirItem()
0387         {
0388             if (autoUpdates) {
0389                 if (KDirWatch::exists() && url.isLocalFile()) {
0390                     KDirWatch::self()->removeDir(m_canonicalPath);
0391                 }
0392                 // Since sendSignal goes through D-Bus, QCoreApplication has to be available
0393                 // which might not be the case anymore from a global static dtor like the
0394                 // lister cache
0395                 if (QCoreApplication::instance()) {
0396                     sendSignal(false, url);
0397                 }
0398             }
0399             lstItems.clear();
0400         }
0401 
0402         DirItem(const DirItem &) = delete;
0403         DirItem &operator=(const DirItem &) = delete;
0404 
0405         void sendSignal(bool entering, const QUrl &url)
0406         {
0407             // Note that "entering" means "start watching", and "leaving" means "stop watching"
0408             // (i.e. it's not when the user leaves the directory, it's when the directory is removed from the cache)
0409 #ifndef KIO_ANDROID_STUB
0410             if (entering) {
0411                 org::kde::KDirNotify::emitEnteredDirectory(url);
0412             } else {
0413                 org::kde::KDirNotify::emitLeftDirectory(url);
0414             }
0415 #endif
0416         }
0417 
0418         void redirect(const QUrl &newUrl)
0419         {
0420             if (autoUpdates) {
0421                 if (url.isLocalFile()) {
0422                     KDirWatch::self()->removeDir(m_canonicalPath);
0423                 }
0424                 sendSignal(false, url);
0425 
0426                 if (newUrl.isLocalFile()) {
0427                     m_canonicalPath = QFileInfo(newUrl.toLocalFile()).canonicalFilePath();
0428                     KDirWatch::self()->addDir(m_canonicalPath);
0429                 }
0430                 sendSignal(true, newUrl);
0431             }
0432 
0433             url = newUrl;
0434 
0435             if (!rootItem.isNull()) {
0436                 rootItem.setUrl(newUrl);
0437             }
0438         }
0439 
0440         void incAutoUpdate()
0441         {
0442             if (autoUpdates++ == 0) {
0443                 if (url.isLocalFile()) {
0444                     KDirWatch::self()->addDir(m_canonicalPath);
0445                 }
0446                 sendSignal(true, url);
0447             }
0448         }
0449 
0450         void decAutoUpdate()
0451         {
0452             if (--autoUpdates == 0) {
0453                 if (url.isLocalFile()) {
0454                     KDirWatch::self()->removeDir(m_canonicalPath);
0455                 }
0456                 sendSignal(false, url);
0457             }
0458 
0459             else if (autoUpdates < 0) {
0460                 autoUpdates = 0;
0461             }
0462         }
0463 
0464         // Insert the item in the sorted list
0465         void insert(const KFileItem &item)
0466         {
0467             auto it = std::lower_bound(lstItems.begin(), lstItems.end(), item.url());
0468             lstItems.insert(it, item);
0469         }
0470 
0471         // Insert the already sorted items in the sorted list
0472         void insertSortedItems(const KFileItemList &items)
0473         {
0474             if (items.isEmpty()) {
0475                 return;
0476             }
0477             lstItems.reserve(lstItems.size() + items.size());
0478             auto it = lstItems.begin();
0479             for (const auto &item : items) {
0480                 it = std::lower_bound(it, lstItems.end(), item.url());
0481                 it = lstItems.insert(it, item);
0482             }
0483         }
0484 
0485         // number of KCoreDirListers using autoUpdate for this dir
0486         short autoUpdates;
0487 
0488         // this directory is up-to-date
0489         bool complete;
0490 
0491         // the directory is watched while being in the cache (useful for proper incAutoUpdate/decAutoUpdate count)
0492         bool watchedWhileInCache;
0493 
0494         // the complete url of this directory
0495         QUrl url;
0496 
0497         // the local path, with symlinks resolved, so that KDirWatch works
0498         QString m_canonicalPath;
0499 
0500         // KFileItem representing the root of this directory.
0501         // Remember that this is optional. FTP sites don't return '.' in
0502         // the list, so they give no root item
0503         KFileItem rootItem;
0504         QList<KFileItem> lstItems;
0505     };
0506 
0507     QMap<KIO::ListJob *, KIO::UDSEntryList> runningListJobs;
0508 
0509     // an item is a complete directory
0510     QHash<QUrl, DirItem *> itemsInUse;
0511     QCache<QUrl, DirItem> itemsCached;
0512 
0513     // cache of ".hidden" files
0514     QCache<QString /*dot hidden file*/, CacheHiddenFile> m_cacheHiddenFiles;
0515 
0516     typedef QHash<QUrl, KCoreDirListerCacheDirectoryData> DirectoryDataHash;
0517     DirectoryDataHash directoryData;
0518 
0519     // Symlink-to-directories are registered here so that we can
0520     // find the url that changed, when kdirwatch tells us about
0521     // changes in the canonical url. (#213799)
0522     QHash<QUrl /*canonical path*/, QList<QUrl> /*dirlister urls*/> canonicalUrls;
0523 
0524     // Set of local files that we have changed recently (according to KDirWatch)
0525     // We temporize the notifications by keeping them 500ms in this list.
0526     std::set<QString /*path*/> pendingUpdates;
0527     std::set<QString /*path*/> pendingDirectoryUpdates;
0528     // The timer for doing the delayed updates
0529     QTimer pendingUpdateTimer;
0530 
0531     // Set of remote files that have changed recently -- but we can't emit those
0532     // changes yet, we need to wait for the "update" directory listing.
0533     // The cmp() call can't differ MIME types since they are determined on demand,
0534     // this is why we need to remember those files here.
0535     std::set<KFileItem> pendingRemoteUpdates;
0536 
0537     // the KDirNotify signals
0538     OrgKdeKDirNotifyInterface *kdirnotify;
0539 
0540     struct ItemInUseChange;
0541 };
0542 
0543 // Data associated with a directory url
0544 // This could be in DirItem but only in the itemsInUse dict...
0545 struct KCoreDirListerCacheDirectoryData {
0546     // A lister can be EITHER in listersCurrentlyListing OR listersCurrentlyHolding
0547     // but NOT in both at the same time.
0548     // But both lists can have different listers at the same time; this
0549     // happens if more listers are requesting url at the same time and
0550     // one lister was stopped during the listing of files.
0551 
0552     // Listers that are currently listing this url
0553     QList<KCoreDirLister *> listersCurrentlyListing;
0554     // Listers that are currently holding this url
0555     QList<KCoreDirLister *> listersCurrentlyHolding;
0556 
0557     void moveListersWithoutCachedItemsJob(const QUrl &url);
0558 };
0559 
0560 // This job tells KCoreDirListerCache to emit cached items asynchronously from listDir()
0561 // to give the KCoreDirLister user enough time for connecting to its signals, and so
0562 // that KCoreDirListerCache behaves just like when a real KIO::Job is used: nothing
0563 // is emitted during the openUrl call itself.
0564 class KCoreDirListerPrivate::CachedItemsJob : public KJob
0565 {
0566     Q_OBJECT
0567 public:
0568     CachedItemsJob(KCoreDirLister *lister, const QUrl &url, bool reload);
0569 
0570     /*reimp*/ void start() override
0571     {
0572         QMetaObject::invokeMethod(this, &KCoreDirListerPrivate::CachedItemsJob::done, Qt::QueuedConnection);
0573     }
0574 
0575     // For updateDirectory() to cancel m_emitCompleted;
0576     void setEmitCompleted(bool b)
0577     {
0578         m_emitCompleted = b;
0579     }
0580 
0581     QUrl url() const
0582     {
0583         return m_url;
0584     }
0585 
0586 protected:
0587     bool doKill() override;
0588 
0589 public Q_SLOTS:
0590     void done();
0591 
0592 private:
0593     KCoreDirLister *m_lister;
0594     QUrl m_url;
0595     bool m_reload;
0596     bool m_emitCompleted;
0597 };
0598 
0599 #endif