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