File indexing completed on 2024-04-28 11:40:59

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