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