File indexing completed on 2024-05-05 12:18:10
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org> 0004 SPDX-FileCopyrightText: 2000 Carsten Pfeiffer <pfeiffer@kde.org> 0005 SPDX-FileCopyrightText: 2003-2005 David Faure <faure@kde.org> 0006 SPDX-FileCopyrightText: 2001-2006 Michael Brade <brade@kde.org> 0007 0008 SPDX-License-Identifier: LGPL-2.0-or-later 0009 */ 0010 0011 #include "kcoredirlister.h" 0012 #include "kcoredirlister_p.h" 0013 0014 #include "../utils_p.h" 0015 #include "kiocoredebug.h" 0016 #include "kmountpoint.h" 0017 #include "kprotocolmanager.h" 0018 #include <kio/listjob.h> 0019 0020 #include <KJobUiDelegate> 0021 #include <KLocalizedString> 0022 0023 #include <QDir> 0024 #include <QFile> 0025 #include <QFileInfo> 0026 #include <QMimeDatabase> 0027 #include <QRegularExpression> 0028 #include <QTextStream> 0029 0030 #include <list> 0031 0032 #include <QLoggingCategory> 0033 Q_DECLARE_LOGGING_CATEGORY(KIO_CORE_DIRLISTER) 0034 Q_LOGGING_CATEGORY(KIO_CORE_DIRLISTER, "kf.kio.core.dirlister", QtWarningMsg) 0035 0036 // Enable this to get printDebug() called often, to see the contents of the cache 0037 // #define DEBUG_CACHE 0038 0039 // Make really sure it doesn't get activated in the final build 0040 #ifdef NDEBUG 0041 #undef DEBUG_CACHE 0042 #endif 0043 0044 Q_GLOBAL_STATIC(KCoreDirListerCache, kDirListerCache) 0045 0046 KCoreDirListerCache::KCoreDirListerCache() 0047 : itemsCached(10) 0048 , // keep the last 10 directories around 0049 m_cacheHiddenFiles(10) // keep the last 10 ".hidden" files around 0050 { 0051 qCDebug(KIO_CORE_DIRLISTER); 0052 0053 connect(&pendingUpdateTimer, &QTimer::timeout, this, &KCoreDirListerCache::processPendingUpdates); 0054 pendingUpdateTimer.setSingleShot(true); 0055 0056 connect(KDirWatch::self(), &KDirWatch::dirty, this, &KCoreDirListerCache::slotFileDirty); 0057 connect(KDirWatch::self(), &KDirWatch::created, this, &KCoreDirListerCache::slotFileCreated); 0058 connect(KDirWatch::self(), &KDirWatch::deleted, this, &KCoreDirListerCache::slotFileDeleted); 0059 0060 #ifndef KIO_ANDROID_STUB 0061 kdirnotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this); 0062 connect(kdirnotify, &org::kde::KDirNotify::FileRenamedWithLocalPath, this, &KCoreDirListerCache::slotFileRenamed); 0063 connect(kdirnotify, &org::kde::KDirNotify::FilesAdded, this, &KCoreDirListerCache::slotFilesAdded); 0064 connect(kdirnotify, &org::kde::KDirNotify::FilesChanged, this, &KCoreDirListerCache::slotFilesChanged); 0065 connect(kdirnotify, &org::kde::KDirNotify::FilesRemoved, this, qOverload<const QStringList &>(&KCoreDirListerCache::slotFilesRemoved)); 0066 #endif 0067 } 0068 0069 KCoreDirListerCache::~KCoreDirListerCache() 0070 { 0071 qCDebug(KIO_CORE_DIRLISTER); 0072 0073 qDeleteAll(itemsInUse); 0074 itemsInUse.clear(); 0075 0076 itemsCached.clear(); 0077 directoryData.clear(); 0078 m_cacheHiddenFiles.clear(); 0079 0080 if (KDirWatch::exists()) { 0081 KDirWatch::self()->disconnect(this); 0082 } 0083 } 0084 0085 // setting _reload to true will emit the old files and 0086 // call updateDirectory 0087 bool KCoreDirListerCache::listDir(KCoreDirLister *lister, const QUrl &dirUrl, bool _keep, bool _reload) 0088 { 0089 QUrl _url(dirUrl); 0090 _url.setPath(QDir::cleanPath(_url.path())); // kill consecutive slashes 0091 0092 // like this we don't have to worry about trailing slashes any further 0093 _url = _url.adjusted(QUrl::StripTrailingSlash); 0094 0095 QString resolved; 0096 if (_url.isLocalFile()) { 0097 // Resolve symlinks (#213799) 0098 const QString local = _url.toLocalFile(); 0099 resolved = QFileInfo(local).canonicalFilePath(); 0100 if (local != resolved) { 0101 canonicalUrls[QUrl::fromLocalFile(resolved)].append(_url); 0102 } 0103 // TODO: remove entry from canonicalUrls again in forgetDirs 0104 // Note: this is why we use a QStringList value in there rather than a std::set: 0105 // we can just remove one entry and not have to worry about other dirlisters 0106 // (the non-unicity of the stringlist gives us the refcounting, basically). 0107 } 0108 0109 qCDebug(KIO_CORE_DIRLISTER) << lister << "url=" << _url << "keep=" << _keep << "reload=" << _reload; 0110 #ifdef DEBUG_CACHE 0111 printDebug(); 0112 #endif 0113 0114 if (!_keep) { 0115 // stop any running jobs for lister 0116 stop(lister, true /*silent*/); 0117 0118 // clear our internal list for lister 0119 forgetDirs(lister); 0120 0121 lister->d->rootFileItem = KFileItem(); 0122 } else if (lister->d->lstDirs.contains(_url)) { 0123 // stop the job listing _url for this lister 0124 stopListingUrl(lister, _url, true /*silent*/); 0125 0126 // remove the _url as well, it will be added in a couple of lines again! 0127 // forgetDirs with three args does not do this 0128 // TODO: think about moving this into forgetDirs 0129 lister->d->lstDirs.removeAll(_url); 0130 0131 // clear _url for lister 0132 forgetDirs(lister, _url, true); 0133 0134 if (lister->d->url == _url) { 0135 lister->d->rootFileItem = KFileItem(); 0136 } 0137 } 0138 0139 lister->d->complete = false; 0140 0141 lister->d->lstDirs.append(_url); 0142 0143 if (lister->d->url.isEmpty() || !_keep) { // set toplevel URL only if not set yet 0144 lister->d->url = _url; 0145 } 0146 0147 DirItem *itemU = itemsInUse.value(_url); 0148 0149 KCoreDirListerCacheDirectoryData &dirData = directoryData[_url]; // find or insert 0150 0151 if (dirData.listersCurrentlyListing.isEmpty()) { 0152 // if there is an update running for _url already we get into 0153 // the following case - it will just be restarted by updateDirectory(). 0154 0155 dirData.listersCurrentlyListing.append(lister); 0156 0157 DirItem *itemFromCache = nullptr; 0158 if (itemU || (!_reload && (itemFromCache = itemsCached.take(_url)))) { 0159 if (itemU) { 0160 qCDebug(KIO_CORE_DIRLISTER) << "Entry already in use:" << _url; 0161 // if _reload is set, then we'll emit cached items and then updateDirectory. 0162 } else { 0163 qCDebug(KIO_CORE_DIRLISTER) << "Entry in cache:" << _url; 0164 itemsInUse.insert(_url, itemFromCache); 0165 itemU = itemFromCache; 0166 } 0167 if (lister->d->autoUpdate) { 0168 itemU->incAutoUpdate(); 0169 } 0170 if (itemFromCache && itemFromCache->watchedWhileInCache) { 0171 // item is promoted from cache, update item autoupdate refcount accordingly 0172 itemFromCache->watchedWhileInCache = false; 0173 itemFromCache->decAutoUpdate(); 0174 } 0175 0176 Q_EMIT lister->started(_url); 0177 0178 // List items from the cache in a delayed manner, just like things would happen 0179 // if we were not using the cache. 0180 new KCoreDirListerPrivate::CachedItemsJob(lister, _url, _reload); 0181 0182 } else { 0183 // dir not in cache or _reload is true 0184 if (_reload) { 0185 qCDebug(KIO_CORE_DIRLISTER) << "Reloading directory:" << _url; 0186 itemsCached.remove(_url); 0187 } else { 0188 qCDebug(KIO_CORE_DIRLISTER) << "Listing directory:" << _url; 0189 } 0190 0191 itemU = new DirItem(_url, resolved); 0192 itemsInUse.insert(_url, itemU); 0193 if (lister->d->autoUpdate) { 0194 itemU->incAutoUpdate(); 0195 } 0196 0197 KIO::ListJob *job = KIO::listDir(_url, KIO::HideProgressInfo); 0198 if (lister->requestMimeTypeWhileListing()) { 0199 job->addMetaData(QStringLiteral("statDetails"), QString::number(KIO::StatDefaultDetails | KIO::StatMimeType)); 0200 } 0201 runningListJobs.insert(job, KIO::UDSEntryList()); 0202 0203 lister->jobStarted(job); 0204 lister->d->connectJob(job); 0205 0206 connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotEntries); 0207 connect(job, &KJob::result, this, &KCoreDirListerCache::slotResult); 0208 connect(job, &KIO::ListJob::redirection, this, &KCoreDirListerCache::slotRedirection); 0209 0210 Q_EMIT lister->started(_url); 0211 0212 qCDebug(KIO_CORE_DIRLISTER) << "Entry now being listed by" << dirData.listersCurrentlyListing; 0213 } 0214 } else { 0215 qCDebug(KIO_CORE_DIRLISTER) << "Entry currently being listed:" << _url << "by" << dirData.listersCurrentlyListing; 0216 #ifdef DEBUG_CACHE 0217 printDebug(); 0218 #endif 0219 0220 Q_EMIT lister->started(_url); 0221 0222 // Maybe listersCurrentlyListing/listersCurrentlyHolding should be QSets? 0223 Q_ASSERT(!dirData.listersCurrentlyListing.contains(lister)); 0224 dirData.listersCurrentlyListing.append(lister); 0225 0226 // a new lister is listing this _url, incr watch refcount 0227 if (lister->d->autoUpdate) { 0228 itemU->incAutoUpdate(); 0229 } 0230 0231 KIO::ListJob *job = jobForUrl(_url); 0232 // job will be 0 if we were listing from cache rather than listing from a kio job. 0233 if (job) { 0234 lister->jobStarted(job); 0235 lister->d->connectJob(job); 0236 } 0237 Q_ASSERT(itemU); 0238 0239 // List existing items in a delayed manner, just like things would happen 0240 // if we were not using the cache. 0241 qCDebug(KIO_CORE_DIRLISTER) << "Listing" << itemU->lstItems.count() << "cached items soon"; 0242 auto *cachedItemsJob = new KCoreDirListerPrivate::CachedItemsJob(lister, _url, _reload); 0243 if (job) { 0244 // The ListJob will take care of emitting completed. 0245 // ### If it finishes before the CachedItemsJob, then we'll emit cached items after completed(), not sure how bad this is. 0246 cachedItemsJob->setEmitCompleted(false); 0247 } 0248 0249 #ifdef DEBUG_CACHE 0250 printDebug(); 0251 #endif 0252 } 0253 0254 return true; 0255 } 0256 0257 KCoreDirListerPrivate::CachedItemsJob *KCoreDirListerPrivate::cachedItemsJobForUrl(const QUrl &url) const 0258 { 0259 for (CachedItemsJob *job : m_cachedItemsJobs) { 0260 if (job->url() == url) { 0261 return job; 0262 } 0263 } 0264 return nullptr; 0265 } 0266 0267 KCoreDirListerPrivate::CachedItemsJob::CachedItemsJob(KCoreDirLister *lister, const QUrl &url, bool reload) 0268 : KJob(lister) 0269 , m_lister(lister) 0270 , m_url(url) 0271 , m_reload(reload) 0272 , m_emitCompleted(true) 0273 { 0274 qCDebug(KIO_CORE_DIRLISTER) << "Creating CachedItemsJob" << this << "for lister" << lister << url; 0275 if (lister->d->cachedItemsJobForUrl(url)) { 0276 qCWarning(KIO_CORE) << "Lister" << lister << "has a cached items job already for" << url; 0277 } 0278 lister->d->m_cachedItemsJobs.append(this); 0279 setAutoDelete(true); 0280 start(); 0281 } 0282 0283 // Called by start() via QueuedConnection 0284 void KCoreDirListerPrivate::CachedItemsJob::done() 0285 { 0286 if (!m_lister) { // job was already killed, but waiting deletion due to deleteLater 0287 return; 0288 } 0289 kDirListerCache()->emitItemsFromCache(this, m_lister, m_url, m_reload, m_emitCompleted); 0290 emitResult(); 0291 } 0292 0293 bool KCoreDirListerPrivate::CachedItemsJob::doKill() 0294 { 0295 qCDebug(KIO_CORE_DIRLISTER) << this; 0296 kDirListerCache()->forgetCachedItemsJob(this, m_lister, m_url); 0297 if (!property("_kdlc_silent").toBool()) { 0298 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 0299 Q_EMIT m_lister->canceled(m_url); 0300 #endif 0301 Q_EMIT m_lister->listingDirCanceled(m_url); 0302 0303 Q_EMIT m_lister->canceled(); 0304 } 0305 m_lister = nullptr; 0306 return true; 0307 } 0308 0309 void KCoreDirListerCache::emitItemsFromCache(KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob, 0310 KCoreDirLister *lister, 0311 const QUrl &_url, 0312 bool _reload, 0313 bool _emitCompleted) 0314 { 0315 lister->d->complete = false; 0316 0317 DirItem *itemU = kDirListerCache()->itemsInUse.value(_url); 0318 if (!itemU) { 0319 qCWarning(KIO_CORE) << "Can't find item for directory" << _url << "anymore"; 0320 } else { 0321 const QList<KFileItem> items = itemU->lstItems; 0322 const KFileItem rootItem = itemU->rootItem; 0323 _reload = _reload || !itemU->complete; 0324 0325 if (lister->d->rootFileItem.isNull() && !rootItem.isNull() && lister->d->url == _url) { 0326 lister->d->rootFileItem = rootItem; 0327 } 0328 if (!items.isEmpty()) { 0329 qCDebug(KIO_CORE_DIRLISTER) << "emitting" << items.count() << "for lister" << lister; 0330 lister->d->addNewItems(_url, items); 0331 lister->d->emitItems(); 0332 } 0333 } 0334 0335 forgetCachedItemsJob(cachedItemsJob, lister, _url); 0336 0337 // Emit completed, unless we were told not to, 0338 // or if listDir() was called while another directory listing for this dir was happening, 0339 // so we "joined" it. We detect that using jobForUrl to ensure it's a real ListJob, 0340 // not just a lister-specific CachedItemsJob (which wouldn't emit completed for us). 0341 if (_emitCompleted) { 0342 lister->d->complete = true; 0343 0344 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 0345 Q_EMIT lister->completed(_url); 0346 #endif 0347 Q_EMIT lister->listingDirCompleted(_url); 0348 Q_EMIT lister->completed(); 0349 0350 if (_reload) { 0351 updateDirectory(_url); 0352 } 0353 } 0354 } 0355 0356 void KCoreDirListerCache::forgetCachedItemsJob(KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob, KCoreDirLister *lister, const QUrl &_url) 0357 { 0358 // Modifications to data structures only below this point; 0359 // so that addNewItems is called with a consistent state 0360 0361 lister->d->m_cachedItemsJobs.removeAll(cachedItemsJob); 0362 0363 KCoreDirListerCacheDirectoryData &dirData = directoryData[_url]; 0364 Q_ASSERT(dirData.listersCurrentlyListing.contains(lister)); 0365 0366 KIO::ListJob *listJob = jobForUrl(_url); 0367 if (!listJob) { 0368 Q_ASSERT(!dirData.listersCurrentlyHolding.contains(lister)); 0369 qCDebug(KIO_CORE_DIRLISTER) << "Moving from listing to holding, because no more job" << lister << _url; 0370 dirData.listersCurrentlyHolding.append(lister); 0371 dirData.listersCurrentlyListing.removeAll(lister); 0372 } else { 0373 qCDebug(KIO_CORE_DIRLISTER) << "Still having a listjob" << listJob << ", so not moving to currently-holding."; 0374 } 0375 } 0376 0377 void KCoreDirListerCache::stop(KCoreDirLister *lister, bool silent) 0378 { 0379 qCDebug(KIO_CORE_DIRLISTER) << "lister:" << lister << "silent=" << silent; 0380 0381 const QList<QUrl> urls = lister->d->lstDirs; 0382 for (const QUrl &url : urls) { 0383 stopListingUrl(lister, url, silent); 0384 } 0385 } 0386 0387 void KCoreDirListerCache::stopListingUrl(KCoreDirLister *lister, const QUrl &_u, bool silent) 0388 { 0389 QUrl url(_u); 0390 url = url.adjusted(QUrl::StripTrailingSlash); 0391 0392 KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob = lister->d->cachedItemsJobForUrl(url); 0393 if (cachedItemsJob) { 0394 if (silent) { 0395 cachedItemsJob->setProperty("_kdlc_silent", true); 0396 } 0397 cachedItemsJob->kill(); // removes job from list, too 0398 } 0399 0400 // TODO: consider to stop all the "child jobs" of url as well 0401 qCDebug(KIO_CORE_DIRLISTER) << lister << " url=" << url; 0402 0403 const auto dirit = directoryData.find(url); 0404 if (dirit == directoryData.end()) { 0405 return; 0406 } 0407 KCoreDirListerCacheDirectoryData &dirData = dirit.value(); 0408 if (dirData.listersCurrentlyListing.contains(lister)) { 0409 qCDebug(KIO_CORE_DIRLISTER) << " found lister" << lister << "in list - for" << url; 0410 if (dirData.listersCurrentlyListing.count() == 1) { 0411 // This was the only dirlister interested in the list job -> kill the job 0412 stopListJob(url, silent); 0413 } else { 0414 // Leave the job running for the other dirlisters, just unsubscribe us. 0415 dirData.listersCurrentlyListing.removeAll(lister); 0416 if (!silent) { 0417 Q_EMIT lister->canceled(); 0418 0419 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 0420 Q_EMIT lister->canceled(url); 0421 #endif 0422 Q_EMIT lister->listingDirCanceled(url); 0423 } 0424 } 0425 } 0426 } 0427 0428 // Helper for stop() and stopListingUrl() 0429 void KCoreDirListerCache::stopListJob(const QUrl &url, bool silent) 0430 { 0431 // Old idea: if it's an update job, let's just leave the job running. 0432 // After all, update jobs do run for "listersCurrentlyHolding", 0433 // so there's no reason to kill them just because @p lister is now a holder. 0434 0435 // However it could be a long-running non-local job (e.g. filenamesearch), which 0436 // the user wants to abort, and which will never be used for updating... 0437 // And in any case slotEntries/slotResult is not meant to be called by update jobs. 0438 // So, change of plan, let's kill it after all, in a way that triggers slotResult/slotUpdateResult. 0439 0440 KIO::ListJob *job = jobForUrl(url); 0441 if (job) { 0442 qCDebug(KIO_CORE_DIRLISTER) << "Killing list job" << job << "for" << url; 0443 if (silent) { 0444 job->setProperty("_kdlc_silent", true); 0445 } 0446 job->kill(KJob::EmitResult); 0447 } 0448 } 0449 0450 void KCoreDirListerCache::setAutoUpdate(KCoreDirLister *lister, bool enable) 0451 { 0452 // IMPORTANT: this method does not check for the current autoUpdate state! 0453 0454 for (const QUrl &url : std::as_const(lister->d->lstDirs)) { 0455 DirItem *dirItem = itemsInUse.value(url); 0456 Q_ASSERT(dirItem); 0457 if (enable) { 0458 dirItem->incAutoUpdate(); 0459 } else { 0460 dirItem->decAutoUpdate(); 0461 } 0462 } 0463 } 0464 0465 void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister) 0466 { 0467 qCDebug(KIO_CORE_DIRLISTER) << lister; 0468 0469 Q_EMIT lister->clear(); 0470 // clear lister->d->lstDirs before calling forgetDirs(), so that 0471 // it doesn't contain things that itemsInUse doesn't. When emitting 0472 // the canceled signals, lstDirs must not contain anything that 0473 // itemsInUse does not contain. (otherwise it might crash in findByName()). 0474 const QList<QUrl> lstDirsCopy = lister->d->lstDirs; 0475 lister->d->lstDirs.clear(); 0476 0477 qCDebug(KIO_CORE_DIRLISTER) << "Iterating over dirs" << lstDirsCopy; 0478 for (const QUrl &dir : lstDirsCopy) { 0479 forgetDirs(lister, dir, false); 0480 } 0481 } 0482 0483 static bool manually_mounted(const QString &path, const KMountPoint::List &possibleMountPoints) 0484 { 0485 KMountPoint::Ptr mp = possibleMountPoints.findByPath(path); 0486 if (!mp) { // not listed in fstab -> yes, manually mounted 0487 if (possibleMountPoints.isEmpty()) { // no fstab at all -> don't assume anything 0488 return false; 0489 } 0490 return true; 0491 } 0492 // noauto -> manually mounted. Otherwise, mounted at boot time, won't be unmounted any time soon hopefully. 0493 return mp->mountOptions().contains(QLatin1String("noauto")); 0494 } 0495 0496 void KCoreDirListerCache::forgetDirs(KCoreDirLister *lister, const QUrl &_url, bool notify) 0497 { 0498 qCDebug(KIO_CORE_DIRLISTER) << lister << " _url: " << _url; 0499 0500 const QUrl url = _url.adjusted(QUrl::StripTrailingSlash); 0501 0502 DirectoryDataHash::iterator dit = directoryData.find(url); 0503 if (dit == directoryData.end()) { 0504 return; 0505 } 0506 KCoreDirListerCacheDirectoryData &dirData = *dit; 0507 dirData.listersCurrentlyHolding.removeAll(lister); 0508 0509 // This lister doesn't care for updates running in <url> anymore 0510 KIO::ListJob *job = jobForUrl(url); 0511 if (job) { 0512 lister->d->jobDone(job); 0513 } 0514 0515 DirItem *item = itemsInUse.value(url); 0516 Q_ASSERT(item); 0517 bool insertIntoCache = false; 0518 0519 if (dirData.listersCurrentlyHolding.isEmpty() && dirData.listersCurrentlyListing.isEmpty()) { 0520 // item not in use anymore -> move into cache if complete 0521 directoryData.erase(dit); 0522 itemsInUse.remove(url); 0523 0524 // this job is a running update which nobody cares about anymore 0525 if (job) { 0526 killJob(job); 0527 qCDebug(KIO_CORE_DIRLISTER) << "Killing update job for " << url; 0528 0529 // Well, the user of KCoreDirLister doesn't really care that we're stopping 0530 // a background-running job from a previous URL (in listDir) -> commented out. 0531 // stop() already emitted canceled. 0532 // emit lister->canceled( url ); 0533 if (lister->d->numJobs() == 0) { 0534 lister->d->complete = true; 0535 // emit lister->canceled(); 0536 } 0537 } 0538 0539 if (notify) { 0540 lister->d->lstDirs.removeAll(url); 0541 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 0542 Q_EMIT lister->clear(url); 0543 #endif 0544 Q_EMIT lister->clearDir(url); 0545 } 0546 0547 insertIntoCache = item->complete; 0548 if (insertIntoCache) { 0549 // TODO(afiestas): remove use of KMountPoint+manually_mounted and port to Solid: 0550 // 1) find Volume for the local path "item->url.toLocalFile()" (which could be anywhere 0551 // under the mount point) -- probably needs a new operator in libsolid query parser 0552 // 2) [**] becomes: if (Drive is hotpluggable or Volume is removable) "set to dirty" else "keep watch" 0553 const KMountPoint::List possibleMountPoints = KMountPoint::possibleMountPoints(KMountPoint::NeedMountOptions); 0554 0555 // Should we forget the dir for good, or keep a watch on it? 0556 // Generally keep a watch, except when it would prevent 0557 // unmounting a removable device (#37780) 0558 const bool isLocal = item->url.isLocalFile(); 0559 bool isManuallyMounted = false; 0560 bool containsManuallyMounted = false; 0561 if (isLocal) { 0562 isManuallyMounted = manually_mounted(item->url.toLocalFile(), possibleMountPoints); 0563 if (!isManuallyMounted) { 0564 // Look for a manually-mounted directory inside 0565 // If there's one, we can't keep a watch either, FAM would prevent unmounting the CDROM 0566 // I hope this isn't too slow 0567 auto kit = item->lstItems.constBegin(); 0568 const auto kend = item->lstItems.constEnd(); 0569 for (; kit != kend && !containsManuallyMounted; ++kit) { 0570 if ((*kit).isDir() && manually_mounted((*kit).url().toLocalFile(), possibleMountPoints)) { 0571 containsManuallyMounted = true; 0572 } 0573 } 0574 } 0575 } 0576 0577 if (isManuallyMounted || containsManuallyMounted) { // [**] 0578 qCDebug(KIO_CORE_DIRLISTER) << "Not adding a watch on " << item->url << " because it " 0579 << (isManuallyMounted ? "is manually mounted" : "contains a manually mounted subdir"); 0580 item->complete = false; // set to "dirty" 0581 } else { 0582 item->incAutoUpdate(); // keep watch, incr refcount to account for cache 0583 item->watchedWhileInCache = true; 0584 } 0585 } else { 0586 delete item; 0587 item = nullptr; 0588 } 0589 } 0590 0591 if (item && lister->d->autoUpdate) { 0592 item->decAutoUpdate(); 0593 } 0594 0595 // Inserting into QCache must be done last, since it might delete the item 0596 if (item && insertIntoCache) { 0597 qCDebug(KIO_CORE_DIRLISTER) << lister << "item moved into cache:" << url; 0598 itemsCached.insert(url, item); 0599 } 0600 } 0601 0602 void KCoreDirListerCache::updateDirectory(const QUrl &_dir) 0603 { 0604 qCDebug(KIO_CORE_DIRLISTER) << _dir; 0605 0606 QUrl dir = _dir.adjusted(QUrl::StripTrailingSlash); 0607 if (!checkUpdate(dir)) { 0608 auto parentDir = dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 0609 if (checkUpdate(parentDir)) { 0610 // if the parent is in use, update it instead 0611 dir = parentDir; 0612 } else { 0613 return; 0614 } 0615 } 0616 0617 // A job can be running to 0618 // - only list a new directory: the listers are in listersCurrentlyListing 0619 // - only update a directory: the listers are in listersCurrentlyHolding 0620 // - update a currently running listing: the listers are in both 0621 0622 KCoreDirListerCacheDirectoryData &dirData = directoryData[dir]; 0623 const QList<KCoreDirLister *> listers = dirData.listersCurrentlyListing; 0624 const QList<KCoreDirLister *> holders = dirData.listersCurrentlyHolding; 0625 0626 qCDebug(KIO_CORE_DIRLISTER) << dir << "listers=" << listers << "holders=" << holders; 0627 0628 bool killed = false; 0629 KIO::ListJob *job = jobForUrl(dir); 0630 if (job) { 0631 // the job is running already, tell it to do another update at the end 0632 // (don't kill it, we would keep doing that during a long download to a slow sshfs mount) 0633 job->setProperty("need_another_update", true); 0634 return; 0635 } else { 0636 // Emit any cached items. 0637 // updateDirectory() is about the diff compared to the cached items... 0638 for (const KCoreDirLister *kdl : listers) { 0639 KCoreDirListerPrivate::CachedItemsJob *cachedItemsJob = kdl->d->cachedItemsJobForUrl(dir); 0640 if (cachedItemsJob) { 0641 cachedItemsJob->setEmitCompleted(false); 0642 cachedItemsJob->done(); // removes from cachedItemsJobs list 0643 delete cachedItemsJob; 0644 killed = true; 0645 } 0646 } 0647 } 0648 qCDebug(KIO_CORE_DIRLISTER) << "Killed=" << killed; 0649 0650 // we don't need to emit canceled signals since we only replaced the job, 0651 // the listing is continuing. 0652 0653 if (!(listers.isEmpty() || killed)) { 0654 qCWarning(KIO_CORE) << "The unexpected happened."; 0655 qCWarning(KIO_CORE) << "listers for" << dir << "=" << listers; 0656 qCWarning(KIO_CORE) << "job=" << job; 0657 for (const KCoreDirLister *kdl : listers) { 0658 qCDebug(KIO_CORE_DIRLISTER) << "lister" << kdl << "m_cachedItemsJobs=" << kdl->d->m_cachedItemsJobs; 0659 } 0660 #ifndef NDEBUG 0661 printDebug(); 0662 #endif 0663 } 0664 Q_ASSERT(listers.isEmpty() || killed); 0665 0666 job = KIO::listDir(dir, KIO::HideProgressInfo); 0667 runningListJobs.insert(job, KIO::UDSEntryList()); 0668 0669 const bool requestFromListers = std::any_of(listers.cbegin(), listers.cend(), [](KCoreDirLister *lister) { 0670 return lister->requestMimeTypeWhileListing(); 0671 }); 0672 const bool requestFromholders = std::any_of(holders.cbegin(), holders.cend(), [](KCoreDirLister *lister) { 0673 return lister->requestMimeTypeWhileListing(); 0674 }); 0675 0676 if (requestFromListers || requestFromholders) { 0677 job->addMetaData(QStringLiteral("statDetails"), QString::number(KIO::StatDefaultDetails | KIO::StatMimeType)); 0678 } 0679 0680 connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotUpdateEntries); 0681 connect(job, &KJob::result, this, &KCoreDirListerCache::slotUpdateResult); 0682 0683 qCDebug(KIO_CORE_DIRLISTER) << "update started in" << dir; 0684 0685 for (KCoreDirLister *kdl : listers) { 0686 kdl->jobStarted(job); 0687 } 0688 0689 if (!holders.isEmpty()) { 0690 if (!killed) { 0691 for (KCoreDirLister *kdl : holders) { 0692 kdl->jobStarted(job); 0693 Q_EMIT kdl->started(dir); 0694 } 0695 } else { 0696 for (KCoreDirLister *kdl : holders) { 0697 kdl->jobStarted(job); 0698 } 0699 } 0700 } 0701 } 0702 0703 bool KCoreDirListerCache::checkUpdate(const QUrl &_dir) 0704 { 0705 if (!itemsInUse.contains(_dir)) { 0706 DirItem *item = itemsCached[_dir]; 0707 if (item && item->complete) { 0708 item->complete = false; 0709 item->watchedWhileInCache = false; 0710 item->decAutoUpdate(); 0711 qCDebug(KIO_CORE_DIRLISTER) << "directory " << _dir << " not in use, marked dirty."; 0712 } 0713 // else 0714 qCDebug(KIO_CORE_DIRLISTER) << "aborted, directory " << _dir << " not in cache."; 0715 return false; 0716 } else { 0717 return true; 0718 } 0719 } 0720 0721 KFileItem KCoreDirListerCache::itemForUrl(const QUrl &url) const 0722 { 0723 return findByUrl(nullptr, url); 0724 } 0725 0726 KCoreDirListerCache::DirItem *KCoreDirListerCache::dirItemForUrl(const QUrl &dir) const 0727 { 0728 const QUrl url = dir.adjusted(QUrl::StripTrailingSlash); 0729 DirItem *item = itemsInUse.value(url); 0730 if (!item) { 0731 item = itemsCached[url]; 0732 } 0733 return item; 0734 } 0735 0736 QList<KFileItem> *KCoreDirListerCache::itemsForDir(const QUrl &dir) const 0737 { 0738 DirItem *item = dirItemForUrl(dir); 0739 return item ? &item->lstItems : nullptr; 0740 } 0741 0742 KFileItem KCoreDirListerCache::findByName(const KCoreDirLister *lister, const QString &_name) const 0743 { 0744 Q_ASSERT(lister); 0745 0746 auto isMatch = [&_name](const KFileItem &item) { 0747 return _name == item.name(); 0748 }; 0749 0750 for (const auto &dirUrl : std::as_const(lister->d->lstDirs)) { 0751 DirItem *dirItem = itemsInUse.value(dirUrl); 0752 Q_ASSERT(dirItem); 0753 0754 auto it = std::find_if(dirItem->lstItems.cbegin(), dirItem->lstItems.cend(), isMatch); 0755 if (it != dirItem->lstItems.cend()) { 0756 return *it; 0757 } 0758 } 0759 0760 return {}; 0761 } 0762 0763 KFileItem KCoreDirListerCache::findByUrl(const KCoreDirLister *lister, const QUrl &_u) const 0764 { 0765 QUrl url(_u); 0766 url = url.adjusted(QUrl::StripTrailingSlash); 0767 0768 const QUrl parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 0769 DirItem *dirItem = dirItemForUrl(parentDir); 0770 if (dirItem) { 0771 // If lister is set, check that it contains this dir 0772 if (!lister || lister->d->lstDirs.contains(parentDir)) { 0773 // Binary search 0774 auto it = std::lower_bound(dirItem->lstItems.begin(), dirItem->lstItems.end(), url); 0775 if (it != dirItem->lstItems.end() && it->url() == url) { 0776 return *it; 0777 } 0778 } 0779 } 0780 0781 // Maybe _u is a directory itself? (see KDirModelTest::testChmodDirectory) 0782 // We check this last, though, we prefer returning a kfileitem with an actual 0783 // name if possible (and we make it '.' for root items later). 0784 dirItem = dirItemForUrl(url); 0785 if (dirItem && !dirItem->rootItem.isNull() && dirItem->rootItem.url() == url) { 0786 // If lister is set, check that it contains this dir 0787 if (!lister || lister->d->lstDirs.contains(url)) { 0788 return dirItem->rootItem; 0789 } 0790 } 0791 0792 return KFileItem(); 0793 } 0794 0795 void KCoreDirListerCache::slotFilesAdded(const QString &dir /*url*/) // from KDirNotify signals 0796 { 0797 QUrl urlDir(dir); 0798 itemsAddedInDirectory(urlDir); 0799 } 0800 0801 void KCoreDirListerCache::itemsAddedInDirectory(const QUrl &urlDir) 0802 { 0803 qCDebug(KIO_CORE_DIRLISTER) << urlDir; 0804 const QList<QUrl> urls = directoriesForCanonicalPath(urlDir); 0805 for (const QUrl &u : urls) { 0806 updateDirectory(u); 0807 } 0808 } 0809 0810 void KCoreDirListerCache::slotFilesRemoved(const QStringList &fileList) // from KDirNotify signals 0811 { 0812 slotFilesRemoved(QUrl::fromStringList(fileList)); 0813 } 0814 0815 void KCoreDirListerCache::slotFilesRemoved(const QList<QUrl> &fileList) 0816 { 0817 qCDebug(KIO_CORE_DIRLISTER) << fileList.count(); 0818 // Group notifications by parent dirs (usually there would be only one parent dir) 0819 QMap<QUrl, KFileItemList> removedItemsByDir; 0820 QList<QUrl> deletedSubdirs; 0821 0822 for (const QUrl &url : fileList) { 0823 const QList<QUrl> dirUrls = directoriesForCanonicalPath(url); 0824 for (const QUrl &dir : dirUrls) { 0825 DirItem *dirItem = dirItemForUrl(dir); // is it a listed directory? 0826 if (dirItem) { 0827 deletedSubdirs.append(dir); 0828 if (!dirItem->rootItem.isNull()) { 0829 removedItemsByDir[url].append(dirItem->rootItem); 0830 } 0831 } 0832 } 0833 0834 const QUrl parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 0835 const QList<QUrl> parentDirUrls = directoriesForCanonicalPath(parentDir); 0836 for (const QUrl &dir : parentDirUrls) { 0837 DirItem *dirItem = dirItemForUrl(dir); 0838 if (!dirItem) { 0839 continue; 0840 } 0841 0842 auto dirItemIt = std::find_if(dirItem->lstItems.begin(), dirItem->lstItems.end(), [&url](const KFileItem &fitem) { 0843 return fitem.name() == url.fileName(); 0844 }); 0845 if (dirItemIt != dirItem->lstItems.end()) { 0846 const KFileItem fileitem = *dirItemIt; 0847 removedItemsByDir[dir].append(fileitem); 0848 // If we found a fileitem, we can test if it's a dir. If not, we'll go to deleteDir just in case. 0849 if (fileitem.isNull() || fileitem.isDir()) { 0850 deletedSubdirs.append(url); 0851 } 0852 dirItem->lstItems.erase(dirItemIt); // remove fileitem from list 0853 } 0854 } 0855 } 0856 0857 for (auto rit = removedItemsByDir.constBegin(), cend = removedItemsByDir.constEnd(); rit != cend; ++rit) { 0858 // Tell the views about it before calling deleteDir. 0859 // They might need the subdirs' file items (see the dirtree). 0860 auto dit = directoryData.constFind(rit.key()); 0861 if (dit != directoryData.constEnd()) { 0862 itemsDeleted((*dit).listersCurrentlyHolding, rit.value()); 0863 } 0864 } 0865 0866 for (const QUrl &url : std::as_const(deletedSubdirs)) { 0867 // in case of a dir, check if we have any known children, there's much to do in that case 0868 // (stopping jobs, removing dirs from cache etc.) 0869 deleteDir(url); 0870 } 0871 } 0872 0873 void KCoreDirListerCache::slotFilesChanged(const QStringList &fileList) // from KDirNotify signals 0874 { 0875 qCDebug(KIO_CORE_DIRLISTER) << fileList; 0876 QList<QUrl> dirsToUpdate; 0877 for (const QString &fileUrl : fileList) { 0878 const QUrl url(fileUrl); 0879 const KFileItem &fileitem = findByUrl(nullptr, url); 0880 if (fileitem.isNull()) { 0881 qCDebug(KIO_CORE_DIRLISTER) << "item not found for" << url; 0882 continue; 0883 } 0884 if (url.isLocalFile()) { 0885 pendingUpdates.insert(url.toLocalFile()); // delegate the work to processPendingUpdates 0886 } else { 0887 pendingRemoteUpdates.insert(fileitem); 0888 // For remote files, we won't be able to figure out the new information, 0889 // we have to do a update (directory listing) 0890 const QUrl dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 0891 if (!dirsToUpdate.contains(dir)) { 0892 dirsToUpdate.prepend(dir); 0893 } 0894 } 0895 } 0896 0897 for (const QUrl &dirUrl : std::as_const(dirsToUpdate)) { 0898 updateDirectory(dirUrl); 0899 } 0900 // ## TODO problems with current jobs listing/updating that dir 0901 // ( see kde-2.2.2's kdirlister ) 0902 0903 processPendingUpdates(); 0904 } 0905 0906 void KCoreDirListerCache::slotFileRenamed(const QString &_src, const QString &_dst, const QString &dstPath) // from KDirNotify signals 0907 { 0908 QUrl src(_src); 0909 QUrl dst(_dst); 0910 qCDebug(KIO_CORE_DIRLISTER) << src << "->" << dst; 0911 #ifdef DEBUG_CACHE 0912 printDebug(); 0913 #endif 0914 0915 QUrl oldurl = src.adjusted(QUrl::StripTrailingSlash); 0916 KFileItem fileitem = findByUrl(nullptr, oldurl); 0917 if (fileitem.isNull()) { 0918 qCDebug(KIO_CORE_DIRLISTER) << "Item not found:" << oldurl; 0919 return; 0920 } 0921 0922 const KFileItem oldItem = fileitem; 0923 0924 // Dest already exists? Was overwritten then (testcase: #151851) 0925 // We better emit it as deleted -before- doing the renaming, otherwise 0926 // the "update" mechanism will emit the old one as deleted and 0927 // kdirmodel will delete the new (renamed) one! 0928 const KFileItem &existingDestItem = findByUrl(nullptr, dst); 0929 if (!existingDestItem.isNull()) { 0930 qCDebug(KIO_CORE_DIRLISTER) << dst << "already existed, let's delete it"; 0931 slotFilesRemoved(QList<QUrl>{dst}); 0932 } 0933 0934 // If the item had a UDS_URL as well as UDS_NAME set, the user probably wants 0935 // to be updating the name only (since they can't see the URL). 0936 // Check to see if a URL exists, and if so, if only the file part has changed, 0937 // only update the name and not the underlying URL. 0938 bool nameOnly = !fileitem.entry().stringValue(KIO::UDSEntry::UDS_URL).isEmpty(); 0939 nameOnly = nameOnly && src.adjusted(QUrl::RemoveFilename) == dst.adjusted(QUrl::RemoveFilename); 0940 0941 if (!nameOnly && fileitem.isDir()) { 0942 renameDir(oldurl, dst); 0943 // #172945 - if the fileitem was the root item of a DirItem that was just removed from the cache, 0944 // then it's a dangling pointer now... 0945 fileitem = findByUrl(nullptr, oldurl); 0946 if (fileitem.isNull()) { // deleted from cache altogether, #188807 0947 return; 0948 } 0949 } 0950 0951 // Now update the KFileItem representing that file or dir (not exclusive with the above!) 0952 if (!oldItem.isLocalFile() && !oldItem.localPath().isEmpty() 0953 && dstPath.isEmpty()) { // it uses UDS_LOCAL_PATH and we don't know the new path? needs an update then 0954 slotFilesChanged(QStringList{src.toString()}); 0955 } else { 0956 const QUrl &itemOldUrl = fileitem.url(); 0957 if (nameOnly) { 0958 fileitem.setName(dst.fileName()); 0959 } else { 0960 fileitem.setUrl(dst); 0961 } 0962 0963 if (!dstPath.isEmpty()) { 0964 fileitem.setLocalPath(dstPath); 0965 } 0966 0967 fileitem.refreshMimeType(); 0968 fileitem.determineMimeType(); 0969 reinsert(fileitem, itemOldUrl); 0970 0971 const std::set<KCoreDirLister *> listers = emitRefreshItem(oldItem, fileitem); 0972 for (KCoreDirLister *kdl : listers) { 0973 kdl->d->emitItems(); 0974 } 0975 } 0976 0977 #ifdef DEBUG_CACHE 0978 printDebug(); 0979 #endif 0980 } 0981 0982 std::set<KCoreDirLister *> KCoreDirListerCache::emitRefreshItem(const KFileItem &oldItem, const KFileItem &fileitem) 0983 { 0984 qCDebug(KIO_CORE_DIRLISTER) << "old:" << oldItem.name() << oldItem.url() << "new:" << fileitem.name() << fileitem.url(); 0985 // Look whether this item was shown in any view, i.e. held by any dirlister 0986 const QUrl parentDir = oldItem.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 0987 DirectoryDataHash::iterator dit = directoryData.find(parentDir); 0988 QList<KCoreDirLister *> listers; 0989 // Also look in listersCurrentlyListing, in case the user manages to rename during a listing 0990 if (dit != directoryData.end()) { 0991 listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing; 0992 } 0993 if (oldItem.isDir()) { 0994 // For a directory, look for dirlisters where it's the root item. 0995 dit = directoryData.find(oldItem.url()); 0996 if (dit != directoryData.end()) { 0997 listers += (*dit).listersCurrentlyHolding + (*dit).listersCurrentlyListing; 0998 } 0999 } 1000 std::set<KCoreDirLister *> listersToRefresh; 1001 for (KCoreDirLister *kdl : std::as_const(listers)) { 1002 // deduplicate listers 1003 listersToRefresh.insert(kdl); 1004 } 1005 for (KCoreDirLister *kdl : std::as_const(listersToRefresh)) { 1006 // For a directory, look for dirlisters where it's the root item. 1007 QUrl directoryUrl(oldItem.url()); 1008 if (oldItem.isDir() && kdl->d->rootFileItem == oldItem) { 1009 const KFileItem oldRootItem = kdl->d->rootFileItem; 1010 kdl->d->rootFileItem = fileitem; 1011 kdl->d->addRefreshItem(directoryUrl, oldRootItem, fileitem); 1012 } else { 1013 directoryUrl = directoryUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 1014 kdl->d->addRefreshItem(directoryUrl, oldItem, fileitem); 1015 } 1016 } 1017 return listersToRefresh; 1018 } 1019 1020 QList<QUrl> KCoreDirListerCache::directoriesForCanonicalPath(const QUrl &dir) const 1021 { 1022 QList<QUrl> urlList = canonicalUrls.value(dir); 1023 // make unique 1024 if (urlList.size() > 1) { 1025 std::sort(urlList.begin(), urlList.end()); 1026 auto end_unique = std::unique(urlList.begin(), urlList.end()); 1027 urlList.erase(end_unique, urlList.end()); 1028 } 1029 1030 QList<QUrl> dirs({dir}); 1031 dirs.append(urlList); 1032 1033 if (dirs.count() > 1) { 1034 qCDebug(KIO_CORE_DIRLISTER) << dir << "known as" << dirs; 1035 } 1036 return dirs; 1037 } 1038 1039 // private slots 1040 1041 // Called by KDirWatch - usually when a dir we're watching has been modified, 1042 // but it can also be called for a file. 1043 void KCoreDirListerCache::slotFileDirty(const QString &path) 1044 { 1045 qCDebug(KIO_CORE_DIRLISTER) << path; 1046 QUrl url = QUrl::fromLocalFile(path).adjusted(QUrl::StripTrailingSlash); 1047 // File or dir? 1048 bool isDir; 1049 const KFileItem item = itemForUrl(url); 1050 1051 if (!item.isNull()) { 1052 isDir = item.isDir(); 1053 } else { 1054 QFileInfo info(path); 1055 if (!info.exists()) { 1056 return; // error 1057 } 1058 isDir = info.isDir(); 1059 } 1060 1061 if (isDir) { 1062 const QList<QUrl> urls = directoriesForCanonicalPath(url); 1063 for (const QUrl &dir : urls) { 1064 handleDirDirty(dir); 1065 } 1066 } 1067 // Also do this for dirs, e.g. to handle permission changes 1068 const QList<QUrl> urls = directoriesForCanonicalPath(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)); 1069 for (const QUrl &dir : urls) { 1070 QUrl aliasUrl(dir); 1071 aliasUrl.setPath(Utils::concatPaths(aliasUrl.path(), url.fileName())); 1072 handleFileDirty(aliasUrl); 1073 } 1074 } 1075 1076 // Called by slotFileDirty 1077 void KCoreDirListerCache::handleDirDirty(const QUrl &url) 1078 { 1079 // A dir: launch an update job if anyone cares about it 1080 1081 // This also means we can forget about pending updates to individual files in that dir 1082 const QString dir = url.toLocalFile(); 1083 const QString dirPath = Utils::slashAppended(dir); 1084 1085 for (auto pendingIt = pendingUpdates.cbegin(); pendingIt != pendingUpdates.cend(); /* */) { 1086 const QString updPath = *pendingIt; 1087 qCDebug(KIO_CORE_DIRLISTER) << "had pending update" << updPath; 1088 if (updPath.startsWith(dirPath) && updPath.indexOf(QLatin1Char('/'), dirPath.length()) == -1) { // direct child item 1089 qCDebug(KIO_CORE_DIRLISTER) << "forgetting about individual update to" << updPath; 1090 pendingIt = pendingUpdates.erase(pendingIt); 1091 } else { 1092 ++pendingIt; 1093 } 1094 } 1095 1096 if (checkUpdate(url)) { 1097 const auto [it, isInserted] = pendingDirectoryUpdates.insert(dir); 1098 if (isInserted && !pendingUpdateTimer.isActive()) { 1099 pendingUpdateTimer.start(200); 1100 } 1101 } 1102 } 1103 1104 // Called by slotFileDirty, for every alias of <url> 1105 void KCoreDirListerCache::handleFileDirty(const QUrl &url) 1106 { 1107 // A file: do we know about it already? 1108 const KFileItem &existingItem = findByUrl(nullptr, url); 1109 const QUrl dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 1110 if (existingItem.isNull()) { 1111 // No - update the parent dir then 1112 handleDirDirty(dir); 1113 } 1114 1115 // Delay updating the file, FAM is flooding us with events 1116 if (checkUpdate(dir)) { 1117 const QString filePath = url.toLocalFile(); 1118 const auto [it, isInserted] = pendingUpdates.insert(filePath); 1119 if (isInserted && !pendingUpdateTimer.isActive()) { 1120 pendingUpdateTimer.start(200); 1121 } 1122 } 1123 } 1124 1125 void KCoreDirListerCache::slotFileCreated(const QString &path) // from KDirWatch 1126 { 1127 qCDebug(KIO_CORE_DIRLISTER) << path; 1128 // XXX: how to avoid a complete rescan here? 1129 // We'd need to stat that one file separately and refresh the item(s) for it. 1130 QUrl fileUrl(QUrl::fromLocalFile(path)); 1131 itemsAddedInDirectory(fileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)); 1132 } 1133 1134 void KCoreDirListerCache::slotFileDeleted(const QString &path) // from KDirWatch 1135 { 1136 qCDebug(KIO_CORE_DIRLISTER) << path; 1137 const QString fileName = QFileInfo(path).fileName(); 1138 QUrl dirUrl(QUrl::fromLocalFile(path)); 1139 QStringList fileUrls; 1140 const QList<QUrl> urls = directoriesForCanonicalPath(dirUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)); 1141 for (const QUrl &url : urls) { 1142 QUrl urlInfo(url); 1143 urlInfo.setPath(Utils::concatPaths(urlInfo.path(), fileName)); 1144 fileUrls << urlInfo.toString(); 1145 } 1146 slotFilesRemoved(fileUrls); 1147 } 1148 1149 void KCoreDirListerCache::slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries) 1150 { 1151 QUrl url(joburl(static_cast<KIO::ListJob *>(job))); 1152 url = url.adjusted(QUrl::StripTrailingSlash); 1153 1154 qCDebug(KIO_CORE_DIRLISTER) << "new entries for " << url; 1155 1156 DirItem *dir = itemsInUse.value(url); 1157 if (!dir) { 1158 qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but itemsInUse only knows about" << itemsInUse.keys(); 1159 Q_ASSERT(dir); 1160 return; 1161 } 1162 1163 DirectoryDataHash::iterator dit = directoryData.find(url); 1164 if (dit == directoryData.end()) { 1165 qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but directoryData doesn't know about that url, only about:" << directoryData.keys(); 1166 Q_ASSERT(dit != directoryData.end()); 1167 return; 1168 } 1169 KCoreDirListerCacheDirectoryData &dirData = *dit; 1170 const QList<KCoreDirLister *> listers = dirData.listersCurrentlyListing; 1171 if (listers.isEmpty()) { 1172 qCWarning(KIO_CORE) << "Internal error: job is listing" << url << "but directoryData says no listers are currently listing " << url; 1173 #ifndef NDEBUG 1174 printDebug(); 1175 #endif 1176 Q_ASSERT(!listers.isEmpty()); 1177 return; 1178 } 1179 1180 // check if anyone wants the MIME types immediately 1181 bool delayedMimeTypes = true; 1182 for (const KCoreDirLister *kdl : listers) { 1183 delayedMimeTypes &= kdl->d->delayedMimeTypes; 1184 } 1185 1186 CacheHiddenFile *cachedHidden = nullptr; 1187 bool dotHiddenChecked = false; 1188 KFileItemList newItems; 1189 for (const auto &entry : entries) { 1190 const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME); 1191 1192 Q_ASSERT(!name.isEmpty()); 1193 if (name.isEmpty()) { 1194 continue; 1195 } 1196 1197 if (name == QLatin1Char('.')) { 1198 // Try to reuse an existing KFileItem (if we listed the parent dir) 1199 // rather than creating a new one. There are many reasons: 1200 // 1) renames and permission changes to the item would have to emit the signals 1201 // twice, otherwise, so that both views manage to recognize the item. 1202 // 2) with kio_ftp we can only know that something is a symlink when 1203 // listing the parent, so prefer that item, which has more info. 1204 // Note that it gives a funky name() to the root item, rather than "." ;) 1205 dir->rootItem = itemForUrl(url); 1206 if (dir->rootItem.isNull()) { 1207 dir->rootItem = KFileItem(entry, url, delayedMimeTypes, true); 1208 } 1209 1210 for (KCoreDirLister *kdl : listers) { 1211 if (kdl->d->rootFileItem.isNull() && kdl->d->url == url) { 1212 kdl->d->rootFileItem = dir->rootItem; 1213 } 1214 } 1215 } else if (name != QLatin1String("..")) { 1216 KFileItem item(entry, url, delayedMimeTypes, true); 1217 1218 // get the names of the files listed in ".hidden", if it exists and is a local file 1219 if (!dotHiddenChecked) { 1220 const QString localPath = item.localPath(); 1221 if (!localPath.isEmpty()) { 1222 const QString rootItemPath = QFileInfo(localPath).absolutePath(); 1223 cachedHidden = cachedDotHiddenForDir(rootItemPath); 1224 } 1225 dotHiddenChecked = true; 1226 } 1227 1228 // hide file if listed in ".hidden" 1229 if (cachedHidden && cachedHidden->listedFiles.find(name) != cachedHidden->listedFiles.cend()) { 1230 item.setHidden(); 1231 } 1232 1233 qCDebug(KIO_CORE_DIRLISTER) << "Adding item: " << item.url(); 1234 newItems.append(item); 1235 } 1236 } 1237 1238 // sort by url using KFileItem::operator< 1239 std::sort(newItems.begin(), newItems.end()); 1240 1241 // Add the items sorted by url, needed by findByUrl 1242 dir->insertSortedItems(newItems); 1243 1244 for (KCoreDirLister *kdl : listers) { 1245 kdl->d->addNewItems(url, newItems); 1246 } 1247 1248 for (KCoreDirLister *kdl : listers) { 1249 kdl->d->emitItems(); 1250 } 1251 } 1252 1253 void KCoreDirListerCache::slotResult(KJob *j) 1254 { 1255 #ifdef DEBUG_CACHE 1256 // printDebug(); 1257 #endif 1258 1259 Q_ASSERT(j); 1260 KIO::ListJob *job = static_cast<KIO::ListJob *>(j); 1261 runningListJobs.remove(job); 1262 1263 QUrl jobUrl(joburl(job)); 1264 jobUrl = jobUrl.adjusted(QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections 1265 1266 qCDebug(KIO_CORE_DIRLISTER) << "finished listing" << jobUrl; 1267 1268 const auto dit = directoryData.find(jobUrl); 1269 if (dit == directoryData.end()) { 1270 qCWarning(KIO_CORE) << "Nothing found in directoryData for URL" << jobUrl; 1271 #ifndef NDEBUG 1272 printDebug(); 1273 #endif 1274 Q_ASSERT(dit != directoryData.end()); 1275 return; 1276 } 1277 KCoreDirListerCacheDirectoryData &dirData = *dit; 1278 if (dirData.listersCurrentlyListing.isEmpty()) { 1279 qCWarning(KIO_CORE) << "OOOOPS, nothing in directoryData.listersCurrentlyListing for" << jobUrl; 1280 // We're about to assert; dump the current state... 1281 #ifndef NDEBUG 1282 printDebug(); 1283 #endif 1284 Q_ASSERT(!dirData.listersCurrentlyListing.isEmpty()); 1285 } 1286 const QList<KCoreDirLister *> listers = dirData.listersCurrentlyListing; 1287 1288 // move all listers to the holding list, do it before emitting 1289 // the signals to make sure it exists in KCoreDirListerCache in case someone 1290 // calls listDir during the signal emission 1291 Q_ASSERT(dirData.listersCurrentlyHolding.isEmpty()); 1292 dirData.moveListersWithoutCachedItemsJob(jobUrl); 1293 1294 if (job->error()) { 1295 bool errorShown = false; 1296 for (KCoreDirLister *kdl : listers) { 1297 kdl->d->jobDone(job); 1298 if (job->error() != KJob::KilledJobError) { 1299 Q_EMIT kdl->jobError(job); 1300 if (kdl->d->m_autoErrorHandling && !errorShown) { 1301 errorShown = true; // do it only once 1302 if (job->uiDelegate()) { 1303 job->uiDelegate()->showErrorMessage(); 1304 } 1305 } 1306 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 82) 1307 kdl->handleError(job); 1308 #endif 1309 } 1310 const bool silent = job->property("_kdlc_silent").toBool(); 1311 if (!silent) { 1312 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 1313 Q_EMIT kdl->canceled(jobUrl); 1314 #endif 1315 Q_EMIT kdl->listingDirCanceled(jobUrl); 1316 } 1317 1318 if (kdl->d->numJobs() == 0) { 1319 kdl->d->complete = true; 1320 if (!silent) { 1321 Q_EMIT kdl->canceled(); 1322 } 1323 } 1324 } 1325 } else { 1326 DirItem *dir = itemsInUse.value(jobUrl); 1327 Q_ASSERT(dir); 1328 dir->complete = true; 1329 1330 for (KCoreDirLister *kdl : listers) { 1331 kdl->d->jobDone(job); 1332 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 1333 Q_EMIT kdl->completed(jobUrl); 1334 #endif 1335 Q_EMIT kdl->listingDirCompleted(jobUrl); 1336 if (kdl->d->numJobs() == 0) { 1337 kdl->d->complete = true; 1338 Q_EMIT kdl->completed(); 1339 } 1340 } 1341 } 1342 1343 // TODO: hmm, if there was an error and job is a parent of one or more 1344 // of the pending urls we should cancel it/them as well 1345 processPendingUpdates(); 1346 1347 if (job->property("need_another_update").toBool()) { 1348 updateDirectory(jobUrl); 1349 } 1350 1351 #ifdef DEBUG_CACHE 1352 printDebug(); 1353 #endif 1354 } 1355 1356 void KCoreDirListerCache::slotRedirection(KIO::Job *j, const QUrl &url) 1357 { 1358 Q_ASSERT(j); 1359 KIO::ListJob *job = static_cast<KIO::ListJob *>(j); 1360 1361 QUrl oldUrl(job->url()); // here we really need the old url! 1362 QUrl newUrl(url); 1363 1364 // strip trailing slashes 1365 oldUrl = oldUrl.adjusted(QUrl::StripTrailingSlash); 1366 newUrl = newUrl.adjusted(QUrl::StripTrailingSlash); 1367 1368 if (oldUrl == newUrl) { 1369 qCDebug(KIO_CORE_DIRLISTER) << "New redirection url same as old, giving up."; 1370 return; 1371 } else if (newUrl.isEmpty()) { 1372 qCDebug(KIO_CORE_DIRLISTER) << "New redirection url is empty, giving up."; 1373 return; 1374 } 1375 1376 qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl; 1377 1378 #ifdef DEBUG_CACHE 1379 // Can't do that here. KCoreDirListerCache::joburl() will use the new url already, 1380 // while our data structures haven't been updated yet -> assert fail. 1381 // printDebug(); 1382 #endif 1383 1384 // I don't think there can be dirItems that are children of oldUrl. 1385 // Am I wrong here? And even if so, we don't need to delete them, right? 1386 // DF: redirection happens before listDir emits any item. Makes little sense otherwise. 1387 1388 // oldUrl cannot be in itemsCached because only completed items are moved there 1389 DirItem *dir = itemsInUse.take(oldUrl); 1390 Q_ASSERT(dir); 1391 1392 DirectoryDataHash::iterator dit = directoryData.find(oldUrl); 1393 Q_ASSERT(dit != directoryData.end()); 1394 KCoreDirListerCacheDirectoryData oldDirData = *dit; 1395 directoryData.erase(dit); 1396 Q_ASSERT(!oldDirData.listersCurrentlyListing.isEmpty()); 1397 const QList<KCoreDirLister *> listers = oldDirData.listersCurrentlyListing; 1398 Q_ASSERT(!listers.isEmpty()); 1399 1400 for (KCoreDirLister *kdl : listers) { 1401 kdl->d->redirect(oldUrl, newUrl, false /*clear items*/); 1402 } 1403 1404 // when a lister was stopped before the job emits the redirection signal, the old url will 1405 // also be in listersCurrentlyHolding 1406 const QList<KCoreDirLister *> holders = oldDirData.listersCurrentlyHolding; 1407 for (KCoreDirLister *kdl : holders) { 1408 kdl->jobStarted(job); 1409 // do it like when starting a new list-job that will redirect later 1410 // TODO: maybe don't emit started if there's an update running for newUrl already? 1411 Q_EMIT kdl->started(oldUrl); 1412 1413 kdl->d->redirect(oldUrl, newUrl, false /*clear items*/); 1414 } 1415 1416 const QList<KCoreDirLister *> allListers = listers + holders; 1417 1418 DirItem *newDir = itemsInUse.value(newUrl); 1419 if (newDir) { 1420 qCDebug(KIO_CORE_DIRLISTER) << newUrl << "already in use"; 1421 1422 // only in this case there can newUrl already be in listersCurrentlyListing or listersCurrentlyHolding 1423 delete dir; 1424 1425 // get the job if one's running for newUrl already (can be a list-job or an update-job), but 1426 // do not return this 'job', which would happen because of the use of redirectionURL() 1427 KIO::ListJob *oldJob = jobForUrl(newUrl, job); 1428 1429 // listers of newUrl with oldJob: forget about the oldJob and use the already running one 1430 // which will be converted to an updateJob 1431 KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; 1432 1433 QList<KCoreDirLister *> &curListers = newDirData.listersCurrentlyListing; 1434 if (!curListers.isEmpty()) { 1435 qCDebug(KIO_CORE_DIRLISTER) << "and it is currently listed"; 1436 1437 Q_ASSERT(oldJob); // ?! 1438 1439 for (KCoreDirLister *kdl : std::as_const(curListers)) { // listers of newUrl 1440 kdl->d->jobDone(oldJob); 1441 1442 kdl->jobStarted(job); 1443 kdl->d->connectJob(job); 1444 } 1445 1446 // append listers of oldUrl with newJob to listers of newUrl with oldJob 1447 for (KCoreDirLister *kdl : listers) { 1448 curListers.append(kdl); 1449 } 1450 } else { 1451 curListers = listers; 1452 } 1453 1454 if (oldJob) { // kill the old job, be it a list-job or an update-job 1455 killJob(oldJob); 1456 } 1457 1458 // holders of newUrl: use the already running job which will be converted to an updateJob 1459 QList<KCoreDirLister *> &curHolders = newDirData.listersCurrentlyHolding; 1460 if (!curHolders.isEmpty()) { 1461 qCDebug(KIO_CORE_DIRLISTER) << "and it is currently held."; 1462 1463 for (KCoreDirLister *kdl : std::as_const(curHolders)) { // holders of newUrl 1464 kdl->jobStarted(job); 1465 Q_EMIT kdl->started(newUrl); 1466 } 1467 1468 // append holders of oldUrl to holders of newUrl 1469 for (KCoreDirLister *kdl : holders) { 1470 curHolders.append(kdl); 1471 } 1472 } else { 1473 curHolders = holders; 1474 } 1475 1476 // emit old items: listers, holders. NOT: newUrlListers/newUrlHolders, they already have them listed 1477 // TODO: make this a separate method? 1478 for (KCoreDirLister *kdl : allListers) { 1479 if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) { 1480 kdl->d->rootFileItem = newDir->rootItem; 1481 } 1482 1483 kdl->d->addNewItems(newUrl, newDir->lstItems); 1484 kdl->d->emitItems(); 1485 } 1486 } else if ((newDir = itemsCached.take(newUrl))) { 1487 qCDebug(KIO_CORE_DIRLISTER) << newUrl << "is unused, but already in the cache."; 1488 1489 delete dir; 1490 itemsInUse.insert(newUrl, newDir); 1491 KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; 1492 newDirData.listersCurrentlyListing = listers; 1493 newDirData.listersCurrentlyHolding = holders; 1494 1495 // emit old items: listers, holders 1496 for (KCoreDirLister *kdl : allListers) { 1497 if (kdl->d->rootFileItem.isNull() && kdl->d->url == newUrl) { 1498 kdl->d->rootFileItem = newDir->rootItem; 1499 } 1500 1501 kdl->d->addNewItems(newUrl, newDir->lstItems); 1502 kdl->d->emitItems(); 1503 } 1504 } else { 1505 qCDebug(KIO_CORE_DIRLISTER) << newUrl << "has not been listed yet."; 1506 1507 dir->rootItem = KFileItem(); 1508 dir->lstItems.clear(); 1509 dir->redirect(newUrl); 1510 itemsInUse.insert(newUrl, dir); 1511 KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; 1512 newDirData.listersCurrentlyListing = listers; 1513 newDirData.listersCurrentlyHolding = holders; 1514 1515 if (holders.isEmpty()) { 1516 #ifdef DEBUG_CACHE 1517 printDebug(); 1518 #endif 1519 return; // only in this case the job doesn't need to be converted, 1520 } 1521 } 1522 1523 // make the job an update job 1524 job->disconnect(this); 1525 1526 connect(job, &KIO::ListJob::entries, this, &KCoreDirListerCache::slotUpdateEntries); 1527 connect(job, &KJob::result, this, &KCoreDirListerCache::slotUpdateResult); 1528 1529 // FIXME: autoUpdate-Counts!! 1530 1531 #ifdef DEBUG_CACHE 1532 printDebug(); 1533 #endif 1534 } 1535 1536 struct KCoreDirListerCache::ItemInUseChange { 1537 ItemInUseChange(const QUrl &old, const QUrl &newU, DirItem *di) 1538 : oldUrl(old) 1539 , newUrl(newU) 1540 , dirItem(di) 1541 { 1542 } 1543 QUrl oldUrl; 1544 QUrl newUrl; 1545 DirItem *dirItem; 1546 }; 1547 1548 void KCoreDirListerCache::renameDir(const QUrl &oldUrl, const QUrl &newUrl) 1549 { 1550 qCDebug(KIO_CORE_DIRLISTER) << oldUrl << "->" << newUrl; 1551 1552 std::vector<ItemInUseChange> itemsToChange; 1553 std::set<KCoreDirLister *> listers; 1554 1555 // Look at all dirs being listed/shown 1556 for (auto itu = itemsInUse.begin(), ituend = itemsInUse.end(); itu != ituend; ++itu) { 1557 DirItem *dir = itu.value(); 1558 const QUrl &oldDirUrl = itu.key(); 1559 qCDebug(KIO_CORE_DIRLISTER) << "itemInUse:" << oldDirUrl; 1560 // Check if this dir is oldUrl, or a subfolder of it 1561 if (oldDirUrl == oldUrl || oldUrl.isParentOf(oldDirUrl)) { 1562 // TODO should use KUrl::cleanpath like isParentOf does 1563 QString relPath = oldDirUrl.path().mid(oldUrl.path().length() + 1); 1564 1565 QUrl newDirUrl(newUrl); // take new base 1566 if (!relPath.isEmpty()) { 1567 newDirUrl.setPath(Utils::concatPaths(newDirUrl.path(), relPath)); // add unchanged relative path 1568 } 1569 qCDebug(KIO_CORE_DIRLISTER) << "new url=" << newDirUrl; 1570 1571 // Update URL in dir item and in itemsInUse 1572 dir->redirect(newDirUrl); 1573 1574 itemsToChange.emplace_back(oldDirUrl.adjusted(QUrl::StripTrailingSlash), newDirUrl.adjusted(QUrl::StripTrailingSlash), dir); 1575 // Rename all items under that dir 1576 // If all items of the directory change the same part of their url, the order is not 1577 // changed, therefore just change it in the list. 1578 for (KFileItem &item : dir->lstItems) { 1579 const KFileItem oldItem = item; 1580 KFileItem newItem = oldItem; 1581 const QUrl &oldItemUrl = oldItem.url(); 1582 QUrl newItemUrl(oldItemUrl); 1583 newItemUrl.setPath(Utils::concatPaths(newDirUrl.path(), oldItemUrl.fileName())); 1584 qCDebug(KIO_CORE_DIRLISTER) << "renaming" << oldItemUrl << "to" << newItemUrl; 1585 newItem.setUrl(newItemUrl); 1586 1587 listers.merge(emitRefreshItem(oldItem, newItem)); 1588 // Change the item 1589 item.setUrl(newItemUrl); 1590 } 1591 } 1592 } 1593 1594 for (KCoreDirLister *kdl : listers) { 1595 kdl->d->emitItems(); 1596 } 1597 1598 // Do the changes to itemsInUse out of the loop to avoid messing up iterators, 1599 // and so that emitRefreshItem can find the stuff in the hash. 1600 for (const ItemInUseChange &i : itemsToChange) { 1601 itemsInUse.remove(i.oldUrl); 1602 itemsInUse.insert(i.newUrl, i.dirItem); 1603 } 1604 // Now that all the caches are updated and consistent, emit the redirection. 1605 for (const ItemInUseChange &i : itemsToChange) { 1606 emitRedirections(QUrl(i.oldUrl), QUrl(i.newUrl)); 1607 } 1608 // Is oldUrl a directory in the cache? 1609 // Remove any child of oldUrl from the cache - even if the renamed dir itself isn't in it! 1610 removeDirFromCache(oldUrl); 1611 // TODO rename, instead. 1612 } 1613 1614 // helper for renameDir, not used for redirections from KIO::listDir(). 1615 void KCoreDirListerCache::emitRedirections(const QUrl &_oldUrl, const QUrl &_newUrl) 1616 { 1617 qCDebug(KIO_CORE_DIRLISTER) << _oldUrl << "->" << _newUrl; 1618 const QUrl oldUrl = _oldUrl.adjusted(QUrl::StripTrailingSlash); 1619 const QUrl newUrl = _newUrl.adjusted(QUrl::StripTrailingSlash); 1620 1621 KIO::ListJob *job = jobForUrl(oldUrl); 1622 if (job) { 1623 killJob(job); 1624 } 1625 1626 // Check if we were listing this dir. Need to abort and restart with new name in that case. 1627 DirectoryDataHash::iterator dit = directoryData.find(oldUrl); 1628 if (dit == directoryData.end()) { 1629 return; 1630 } 1631 const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing; 1632 const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding; 1633 1634 KCoreDirListerCacheDirectoryData &newDirData = directoryData[newUrl]; 1635 1636 // Tell the world that the job listing the old url is dead. 1637 for (KCoreDirLister *kdl : listers) { 1638 if (job) { 1639 kdl->d->jobDone(job); 1640 } 1641 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 1642 Q_EMIT kdl->canceled(oldUrl); 1643 #endif 1644 Q_EMIT kdl->listingDirCanceled(oldUrl); 1645 } 1646 newDirData.listersCurrentlyListing += listers; 1647 1648 // Check if we are currently displaying this directory (odds opposite wrt above) 1649 for (KCoreDirLister *kdl : holders) { 1650 if (job) { 1651 kdl->d->jobDone(job); 1652 } 1653 } 1654 newDirData.listersCurrentlyHolding += holders; 1655 directoryData.erase(dit); 1656 1657 if (!listers.isEmpty()) { 1658 updateDirectory(newUrl); 1659 1660 // Tell the world about the new url 1661 for (KCoreDirLister *kdl : listers) { 1662 Q_EMIT kdl->started(newUrl); 1663 } 1664 } 1665 1666 // And notify the dirlisters of the redirection 1667 for (KCoreDirLister *kdl : holders) { 1668 kdl->d->redirect(oldUrl, newUrl, true /*keep items*/); 1669 } 1670 } 1671 1672 void KCoreDirListerCache::removeDirFromCache(const QUrl &dir) 1673 { 1674 qCDebug(KIO_CORE_DIRLISTER) << dir; 1675 const QList<QUrl> cachedDirs = itemsCached.keys(); // seems slow, but there's no qcache iterator... 1676 for (const QUrl &cachedDir : cachedDirs) { 1677 if (dir == cachedDir || dir.isParentOf(cachedDir)) { 1678 itemsCached.remove(cachedDir); 1679 } 1680 } 1681 } 1682 1683 void KCoreDirListerCache::slotUpdateEntries(KIO::Job *job, const KIO::UDSEntryList &list) 1684 { 1685 runningListJobs[static_cast<KIO::ListJob *>(job)] += list; 1686 } 1687 1688 void KCoreDirListerCache::slotUpdateResult(KJob *j) 1689 { 1690 Q_ASSERT(j); 1691 KIO::ListJob *job = static_cast<KIO::ListJob *>(j); 1692 1693 QUrl jobUrl(joburl(job)); 1694 jobUrl = jobUrl.adjusted(QUrl::StripTrailingSlash); // need remove trailing slashes again, in case of redirections 1695 1696 qCDebug(KIO_CORE_DIRLISTER) << "finished update" << jobUrl; 1697 1698 KCoreDirListerCacheDirectoryData &dirData = directoryData[jobUrl]; 1699 // Collect the dirlisters which were listing the URL using that ListJob 1700 // plus those that were already holding that URL - they all get updated. 1701 dirData.moveListersWithoutCachedItemsJob(jobUrl); 1702 const QList<KCoreDirLister *> listers = dirData.listersCurrentlyHolding + dirData.listersCurrentlyListing; 1703 1704 // once we are updating dirs that are only in the cache this will fail! 1705 Q_ASSERT(!listers.isEmpty()); 1706 1707 if (job->error()) { 1708 for (KCoreDirLister *kdl : listers) { 1709 kdl->d->jobDone(job); 1710 1711 // don't bother the user: no jobError signal emitted 1712 1713 const bool silent = job->property("_kdlc_silent").toBool(); 1714 if (!silent) { 1715 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 1716 Q_EMIT kdl->canceled(jobUrl); 1717 #endif 1718 Q_EMIT kdl->listingDirCanceled(jobUrl); 1719 } 1720 if (kdl->d->numJobs() == 0) { 1721 kdl->d->complete = true; 1722 if (!silent) { 1723 Q_EMIT kdl->canceled(); 1724 } 1725 } 1726 } 1727 1728 runningListJobs.remove(job); 1729 1730 // TODO: if job is a parent of one or more 1731 // of the pending urls we should cancel them 1732 processPendingUpdates(); 1733 return; 1734 } 1735 1736 DirItem *dir = itemsInUse.value(jobUrl, nullptr); 1737 if (!dir) { 1738 qCWarning(KIO_CORE) << "Internal error: itemsInUse did not contain" << jobUrl; 1739 #ifndef NDEBUG 1740 printDebug(); 1741 #endif 1742 Q_ASSERT(dir); 1743 } else { 1744 dir->complete = true; 1745 } 1746 1747 // check if anyone wants the MIME types immediately 1748 bool delayedMimeTypes = true; 1749 for (const KCoreDirLister *kdl : listers) { 1750 delayedMimeTypes &= kdl->d->delayedMimeTypes; 1751 } 1752 1753 typedef QHash<QString, KFileItem> FileItemHash; // fileName -> KFileItem 1754 FileItemHash fileItems; 1755 1756 // Fill the hash from the old list of items. We'll remove entries as we see them 1757 // in the new listing, and the resulting hash entries will be the deleted items. 1758 for (const KFileItem &item : std::as_const(dir->lstItems)) { 1759 fileItems.insert(item.name(), item); 1760 } 1761 1762 CacheHiddenFile *cachedHidden = nullptr; 1763 bool dotHiddenChecked = false; 1764 const KIO::UDSEntryList &buf = runningListJobs.value(job); 1765 KFileItemList newItems; 1766 for (const auto &entry : buf) { 1767 // Form the complete url 1768 KFileItem item(entry, jobUrl, delayedMimeTypes, true); 1769 1770 const QString name = item.name(); 1771 Q_ASSERT(!name.isEmpty()); // A KIO worker setting an empty UDS_NAME is utterly broken, fix the KIO worker! 1772 1773 // we duplicate the check for dotdot here, to avoid iterating over 1774 // all items again and checking in matchesFilter() that way. 1775 if (name.isEmpty() || name == QLatin1String("..")) { 1776 continue; 1777 } 1778 1779 if (name == QLatin1Char('.')) { 1780 // if the update was started before finishing the original listing 1781 // there is no root item yet 1782 if (dir->rootItem.isNull()) { 1783 dir->rootItem = item; 1784 1785 for (KCoreDirLister *kdl : listers) { 1786 if (kdl->d->rootFileItem.isNull() && kdl->d->url == jobUrl) { 1787 kdl->d->rootFileItem = dir->rootItem; 1788 } 1789 } 1790 } 1791 continue; 1792 } else { 1793 // get the names of the files listed in ".hidden", if it exists and is a local file 1794 if (!dotHiddenChecked) { 1795 const QString localPath = item.localPath(); 1796 if (!localPath.isEmpty()) { 1797 const QString rootItemPath = QFileInfo(localPath).absolutePath(); 1798 cachedHidden = cachedDotHiddenForDir(rootItemPath); 1799 } 1800 dotHiddenChecked = true; 1801 } 1802 } 1803 1804 // hide file if listed in ".hidden" 1805 if (cachedHidden && cachedHidden->listedFiles.find(name) != cachedHidden->listedFiles.cend()) { 1806 item.setHidden(); 1807 } 1808 1809 // Find this item 1810 FileItemHash::iterator fiit = fileItems.find(item.name()); 1811 if (fiit != fileItems.end()) { 1812 const KFileItem tmp = fiit.value(); 1813 auto pru_it = pendingRemoteUpdates.find(tmp); 1814 const bool inPendingRemoteUpdates = pru_it != pendingRemoteUpdates.end(); 1815 1816 // check if something changed for this file, using KFileItem::cmp() 1817 if (!tmp.cmp(item) || inPendingRemoteUpdates) { 1818 if (inPendingRemoteUpdates) { 1819 pendingRemoteUpdates.erase(pru_it); 1820 } 1821 1822 qCDebug(KIO_CORE_DIRLISTER) << "file changed:" << tmp.name(); 1823 1824 reinsert(item, tmp.url()); 1825 for (KCoreDirLister *kdl : listers) { 1826 kdl->d->addRefreshItem(jobUrl, tmp, item); 1827 } 1828 } 1829 // Seen, remove 1830 fileItems.erase(fiit); 1831 } else { // this is a new file 1832 qCDebug(KIO_CORE_DIRLISTER) << "new file:" << name; 1833 newItems.append(item); 1834 } 1835 } 1836 1837 // sort by url using KFileItem::operator< 1838 std::sort(newItems.begin(), newItems.end()); 1839 1840 // Add the items sorted by url, needed by findByUrl 1841 dir->insertSortedItems(newItems); 1842 1843 for (KCoreDirLister *kdl : listers) { 1844 kdl->d->addNewItems(jobUrl, newItems); 1845 } 1846 1847 runningListJobs.remove(job); 1848 1849 if (!fileItems.isEmpty()) { 1850 deleteUnmarkedItems(listers, dir->lstItems, fileItems); 1851 } 1852 1853 for (KCoreDirLister *kdl : listers) { 1854 kdl->d->emitItems(); 1855 1856 kdl->d->jobDone(job); 1857 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 1858 Q_EMIT kdl->completed(jobUrl); 1859 #endif 1860 Q_EMIT kdl->listingDirCompleted(jobUrl); 1861 if (kdl->d->numJobs() == 0) { 1862 kdl->d->complete = true; 1863 Q_EMIT kdl->completed(); 1864 } 1865 } 1866 1867 // TODO: hmm, if there was an error and job is a parent of one or more 1868 // of the pending urls we should cancel it/them as well 1869 processPendingUpdates(); 1870 1871 if (job->property("need_another_update").toBool()) { 1872 updateDirectory(jobUrl); 1873 } 1874 } 1875 1876 // private 1877 1878 KIO::ListJob *KCoreDirListerCache::jobForUrl(const QUrl &url, KIO::ListJob *not_job) 1879 { 1880 for (auto it = runningListJobs.cbegin(); it != runningListJobs.cend(); ++it) { 1881 KIO::ListJob *job = it.key(); 1882 const QUrl jobUrl = joburl(job).adjusted(QUrl::StripTrailingSlash); 1883 1884 if (jobUrl == url && job != not_job) { 1885 return job; 1886 } 1887 } 1888 return nullptr; 1889 } 1890 1891 const QUrl &KCoreDirListerCache::joburl(KIO::ListJob *job) 1892 { 1893 if (job->redirectionUrl().isValid()) { 1894 return job->redirectionUrl(); 1895 } else { 1896 return job->url(); 1897 } 1898 } 1899 1900 void KCoreDirListerCache::killJob(KIO::ListJob *job) 1901 { 1902 runningListJobs.remove(job); 1903 job->disconnect(this); 1904 job->kill(); 1905 } 1906 1907 void KCoreDirListerCache::deleteUnmarkedItems(const QList<KCoreDirLister *> &listers, 1908 QList<KFileItem> &lstItems, 1909 const QHash<QString, KFileItem> &itemsToDelete) 1910 { 1911 // Make list of deleted items (for emitting) 1912 KFileItemList deletedItems; 1913 deletedItems.reserve(itemsToDelete.size()); 1914 for (auto kit = itemsToDelete.cbegin(), endIt = itemsToDelete.cend(); kit != endIt; ++kit) { 1915 const KFileItem item = kit.value(); 1916 deletedItems.append(item); 1917 qCDebug(KIO_CORE_DIRLISTER) << "deleted:" << item.name() << item; 1918 } 1919 1920 // Delete all remaining items 1921 auto it = std::remove_if(lstItems.begin(), lstItems.end(), [&itemsToDelete](const KFileItem &item) { 1922 return itemsToDelete.contains(item.name()); 1923 }); 1924 lstItems.erase(it, lstItems.end()); 1925 1926 itemsDeleted(listers, deletedItems); 1927 } 1928 1929 void KCoreDirListerCache::itemsDeleted(const QList<KCoreDirLister *> &listers, const KFileItemList &deletedItems) 1930 { 1931 for (KCoreDirLister *kdl : listers) { 1932 kdl->d->emitItemsDeleted(deletedItems); 1933 } 1934 1935 for (const KFileItem &item : deletedItems) { 1936 if (item.isDir()) { 1937 deleteDir(item.url()); 1938 } 1939 } 1940 } 1941 1942 void KCoreDirListerCache::deleteDir(const QUrl &_dirUrl) 1943 { 1944 qCDebug(KIO_CORE_DIRLISTER) << _dirUrl; 1945 // unregister and remove the children of the deleted item. 1946 // Idea: tell all the KCoreDirListers that they should forget the dir 1947 // and then remove it from the cache. 1948 1949 QUrl dirUrl(_dirUrl.adjusted(QUrl::StripTrailingSlash)); 1950 1951 // Separate itemsInUse iteration and calls to forgetDirs (which modify itemsInUse) 1952 QList<QUrl> affectedItems; 1953 1954 auto itu = itemsInUse.cbegin(); 1955 const auto ituend = itemsInUse.cend(); 1956 for (; itu != ituend; ++itu) { 1957 const QUrl &deletedUrl = itu.key(); 1958 if (dirUrl == deletedUrl || dirUrl.isParentOf(deletedUrl)) { 1959 affectedItems.append(deletedUrl); 1960 } 1961 } 1962 1963 for (const QUrl &deletedUrl : std::as_const(affectedItems)) { 1964 // stop all jobs for deletedUrlStr 1965 auto dit = directoryData.constFind(deletedUrl); 1966 if (dit != directoryData.cend()) { 1967 // we need a copy because stop modifies the list 1968 const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing; 1969 for (KCoreDirLister *kdl : listers) { 1970 stopListingUrl(kdl, deletedUrl); 1971 } 1972 // tell listers holding deletedUrl to forget about it 1973 // this will stop running updates for deletedUrl as well 1974 1975 // we need a copy because forgetDirs modifies the list 1976 const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding; 1977 for (KCoreDirLister *kdl : holders) { 1978 // lister's root is the deleted item 1979 if (kdl->d->url == deletedUrl) { 1980 // tell the view first. It might need the subdirs' items (which forgetDirs will delete) 1981 if (!kdl->d->rootFileItem.isNull()) { 1982 Q_EMIT kdl->itemsDeleted(KFileItemList{kdl->d->rootFileItem}); 1983 } 1984 forgetDirs(kdl); 1985 kdl->d->rootFileItem = KFileItem(); 1986 } else { 1987 const bool treeview = kdl->d->lstDirs.count() > 1; 1988 if (!treeview) { 1989 Q_EMIT kdl->clear(); 1990 kdl->d->lstDirs.clear(); 1991 } else { 1992 kdl->d->lstDirs.removeAll(deletedUrl); 1993 } 1994 1995 forgetDirs(kdl, deletedUrl, treeview); 1996 } 1997 } 1998 } 1999 2000 // delete the entry for deletedUrl - should not be needed, it's in 2001 // items cached now 2002 int count = itemsInUse.remove(deletedUrl); 2003 Q_ASSERT(count == 0); 2004 Q_UNUSED(count); // keep gcc "unused variable" complaining quiet when in release mode 2005 } 2006 2007 // remove the children from the cache 2008 removeDirFromCache(dirUrl); 2009 } 2010 2011 // delayed updating of files, FAM is flooding us with events 2012 void KCoreDirListerCache::processPendingUpdates() 2013 { 2014 std::set<KCoreDirLister *> listers; 2015 for (const QString &file : pendingUpdates) { // always a local path 2016 qCDebug(KIO_CORE_DIRLISTER) << file; 2017 QUrl u = QUrl::fromLocalFile(file); 2018 KFileItem item = findByUrl(nullptr, u); // search all items 2019 if (!item.isNull()) { 2020 // we need to refresh the item, because e.g. the permissions can have changed. 2021 KFileItem oldItem = item; 2022 item.refresh(); 2023 2024 if (!oldItem.cmp(item)) { 2025 reinsert(item, oldItem.url()); 2026 listers.merge(emitRefreshItem(oldItem, item)); 2027 } 2028 } 2029 } 2030 pendingUpdates.clear(); 2031 for (KCoreDirLister *kdl : listers) { 2032 kdl->d->emitItems(); 2033 } 2034 2035 // Directories in need of updating 2036 for (const QString &dir : pendingDirectoryUpdates) { 2037 updateDirectory(QUrl::fromLocalFile(dir)); 2038 } 2039 pendingDirectoryUpdates.clear(); 2040 } 2041 2042 #ifndef NDEBUG 2043 void KCoreDirListerCache::printDebug() 2044 { 2045 qCDebug(KIO_CORE_DIRLISTER) << "Items in use:"; 2046 auto itu = itemsInUse.constBegin(); 2047 const auto ituend = itemsInUse.constEnd(); 2048 for (; itu != ituend; ++itu) { 2049 qCDebug(KIO_CORE_DIRLISTER) << " " << itu.key() << "URL:" << itu.value()->url 2050 << "rootItem:" << (!itu.value()->rootItem.isNull() ? itu.value()->rootItem.url() : QUrl()) 2051 << "autoUpdates refcount:" << itu.value()->autoUpdates << "complete:" << itu.value()->complete 2052 << QStringLiteral("with %1 items.").arg(itu.value()->lstItems.count()); 2053 } 2054 2055 QList<KCoreDirLister *> listersWithoutJob; 2056 qCDebug(KIO_CORE_DIRLISTER) << "Directory data:"; 2057 auto dit = directoryData.constBegin(); 2058 for (; dit != directoryData.constEnd(); ++dit) { 2059 QString list; 2060 const QList<KCoreDirLister *> listers = (*dit).listersCurrentlyListing; 2061 for (KCoreDirLister *listit : listers) { 2062 list += QLatin1String(" 0x") + QString::number(reinterpret_cast<qlonglong>(listit), 16); 2063 } 2064 qCDebug(KIO_CORE_DIRLISTER) << " " << dit.key() << listers.count() << "listers:" << list; 2065 for (KCoreDirLister *listit : listers) { 2066 if (!listit->d->m_cachedItemsJobs.isEmpty()) { 2067 qCDebug(KIO_CORE_DIRLISTER) << " Lister" << listit << "has CachedItemsJobs" << listit->d->m_cachedItemsJobs; 2068 } else if (KIO::ListJob *listJob = jobForUrl(dit.key())) { 2069 qCDebug(KIO_CORE_DIRLISTER) << " Lister" << listit << "has ListJob" << listJob; 2070 } else { 2071 listersWithoutJob.append(listit); 2072 } 2073 } 2074 2075 list.clear(); 2076 const QList<KCoreDirLister *> holders = (*dit).listersCurrentlyHolding; 2077 for (KCoreDirLister *listit : holders) { 2078 list += QLatin1String(" 0x") + QString::number(reinterpret_cast<qlonglong>(listit), 16); 2079 } 2080 qCDebug(KIO_CORE_DIRLISTER) << " " << dit.key() << holders.count() << "holders:" << list; 2081 } 2082 2083 QMap<KIO::ListJob *, KIO::UDSEntryList>::Iterator jit = runningListJobs.begin(); 2084 qCDebug(KIO_CORE_DIRLISTER) << "Jobs:"; 2085 for (; jit != runningListJobs.end(); ++jit) { 2086 qCDebug(KIO_CORE_DIRLISTER) << " " << jit.key() << "listing" << joburl(jit.key()) << ":" << (*jit).count() << "entries."; 2087 } 2088 2089 qCDebug(KIO_CORE_DIRLISTER) << "Items in cache:"; 2090 const QList<QUrl> cachedDirs = itemsCached.keys(); 2091 for (const QUrl &cachedDir : cachedDirs) { 2092 DirItem *dirItem = itemsCached.object(cachedDir); 2093 qCDebug(KIO_CORE_DIRLISTER) << " " << cachedDir 2094 << "rootItem:" << (!dirItem->rootItem.isNull() ? dirItem->rootItem.url().toString() : QStringLiteral("NULL")) << "with" 2095 << dirItem->lstItems.count() << "items."; 2096 } 2097 2098 // Abort on listers without jobs -after- showing the full dump. Easier debugging. 2099 for (KCoreDirLister *listit : std::as_const(listersWithoutJob)) { 2100 qCWarning(KIO_CORE) << "Fatal Error: HUH? Lister" << listit << "is supposed to be listing, but has no job!"; 2101 abort(); 2102 } 2103 } 2104 #endif 2105 2106 KCoreDirLister::KCoreDirLister(QObject *parent) 2107 : QObject(parent) 2108 , d(new KCoreDirListerPrivate(this)) 2109 { 2110 qCDebug(KIO_CORE_DIRLISTER) << "+KCoreDirLister"; 2111 2112 d->complete = true; 2113 2114 setAutoUpdate(true); 2115 setDirOnlyMode(false); 2116 setShowHiddenFiles(false); 2117 } 2118 2119 KCoreDirLister::~KCoreDirLister() 2120 { 2121 qCDebug(KIO_CORE_DIRLISTER) << "~KCoreDirLister" << this; 2122 2123 // Stop all running jobs, remove lister from lists 2124 if (!kDirListerCache.isDestroyed()) { 2125 stop(); 2126 kDirListerCache()->forgetDirs(this); 2127 } 2128 } 2129 2130 // TODO KF6: remove bool ret val, it's always true 2131 bool KCoreDirLister::openUrl(const QUrl &_url, OpenUrlFlags _flags) 2132 { 2133 // emit the current changes made to avoid an inconsistent treeview 2134 if (d->hasPendingChanges && (_flags & Keep)) { 2135 emitChanges(); 2136 } 2137 2138 d->hasPendingChanges = false; 2139 2140 return kDirListerCache()->listDir(this, _url, _flags & Keep, _flags & Reload); 2141 } 2142 2143 void KCoreDirLister::stop() 2144 { 2145 kDirListerCache()->stop(this); 2146 } 2147 2148 void KCoreDirLister::stop(const QUrl &_url) 2149 { 2150 kDirListerCache()->stopListingUrl(this, _url); 2151 } 2152 2153 void KCoreDirLister::forgetDirs(const QUrl &_url) 2154 { 2155 kDirListerCache()->forgetDirs(this, _url, true); 2156 } 2157 2158 bool KCoreDirLister::autoUpdate() const 2159 { 2160 return d->autoUpdate; 2161 } 2162 2163 void KCoreDirLister::setAutoUpdate(bool enable) 2164 { 2165 if (d->autoUpdate == enable) { 2166 return; 2167 } 2168 2169 d->autoUpdate = enable; 2170 kDirListerCache()->setAutoUpdate(this, enable); 2171 } 2172 2173 bool KCoreDirLister::showHiddenFiles() const 2174 { 2175 return d->settings.isShowingDotFiles; 2176 } 2177 2178 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100) 2179 bool KCoreDirLister::showingDotFiles() const 2180 { 2181 return showHiddenFiles(); 2182 } 2183 #endif 2184 2185 void KCoreDirLister::setShowHiddenFiles(bool setShowHiddenFiles) 2186 { 2187 if (d->settings.isShowingDotFiles == setShowHiddenFiles) { 2188 return; 2189 } 2190 2191 d->prepareForSettingsChange(); 2192 d->settings.isShowingDotFiles = setShowHiddenFiles; 2193 } 2194 2195 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100) 2196 void KCoreDirLister::setShowingDotFiles(bool showDotFiles) 2197 { 2198 setShowHiddenFiles(showDotFiles); 2199 } 2200 #endif 2201 2202 bool KCoreDirLister::dirOnlyMode() const 2203 { 2204 return d->settings.dirOnlyMode; 2205 } 2206 2207 void KCoreDirLister::setDirOnlyMode(bool dirsOnly) 2208 { 2209 if (d->settings.dirOnlyMode == dirsOnly) { 2210 return; 2211 } 2212 2213 d->prepareForSettingsChange(); 2214 d->settings.dirOnlyMode = dirsOnly; 2215 } 2216 2217 bool KCoreDirLister::requestMimeTypeWhileListing() const 2218 { 2219 return d->requestMimeTypeWhileListing; 2220 } 2221 2222 void KCoreDirLister::setRequestMimeTypeWhileListing(bool request) 2223 { 2224 if (d->requestMimeTypeWhileListing == request) { 2225 return; 2226 } 2227 2228 d->requestMimeTypeWhileListing = request; 2229 if (d->requestMimeTypeWhileListing) { 2230 // Changing from request off to on, clear any cached items associated 2231 // with this lister so we re-request them and get the mimetype as well. 2232 // If we do not, we risk caching items that have no mime type. 2233 kDirListerCache()->forgetDirs(this); 2234 } 2235 } 2236 2237 QUrl KCoreDirLister::url() const 2238 { 2239 return d->url; 2240 } 2241 2242 QList<QUrl> KCoreDirLister::directories() const 2243 { 2244 return d->lstDirs; 2245 } 2246 2247 void KCoreDirLister::emitChanges() 2248 { 2249 d->emitChanges(); 2250 } 2251 2252 void KCoreDirListerPrivate::emitChanges() 2253 { 2254 if (!hasPendingChanges) { 2255 return; 2256 } 2257 2258 // reset 'hasPendingChanges' now, in case of recursion 2259 // (testcase: enabling recursive scan in ktorrent, #174920) 2260 hasPendingChanges = false; 2261 2262 const KCoreDirListerPrivate::FilterSettings newSettings = settings; 2263 settings = oldSettings; // temporarily 2264 2265 // Fill hash with all items that are currently visible 2266 std::set<QString> oldVisibleItems; 2267 for (const QUrl &dir : std::as_const(lstDirs)) { 2268 const QList<KFileItem> *itemList = kDirListerCache()->itemsForDir(dir); 2269 if (!itemList) { 2270 continue; 2271 } 2272 2273 for (const KFileItem &item : *itemList) { 2274 if (isItemVisible(item) && q->matchesMimeFilter(item)) { 2275 oldVisibleItems.insert(item.name()); 2276 } 2277 } 2278 } 2279 2280 settings = newSettings; 2281 2282 const QList<QUrl> dirs = lstDirs; 2283 for (const QUrl &dir : dirs) { 2284 KFileItemList deletedItems; 2285 2286 const QList<KFileItem> *itemList = kDirListerCache()->itemsForDir(dir); 2287 if (!itemList) { 2288 continue; 2289 } 2290 2291 for (const auto &item : *itemList) { 2292 const QString text = item.text(); 2293 if (text == QLatin1Char('.') || text == QLatin1String("..")) { 2294 continue; 2295 } 2296 const bool wasVisible = oldVisibleItems.find(item.name()) != oldVisibleItems.cend(); 2297 const bool nowVisible = isItemVisible(item) && q->matchesMimeFilter(item); 2298 if (nowVisible && !wasVisible) { 2299 addNewItem(dir, item); // takes care of emitting newItem or itemsFilteredByMime 2300 } else if (!nowVisible && wasVisible) { 2301 deletedItems.append(item); 2302 } 2303 } 2304 if (!deletedItems.isEmpty()) { 2305 Q_EMIT q->itemsDeleted(deletedItems); 2306 } 2307 emitItems(); 2308 } 2309 oldSettings = settings; 2310 } 2311 2312 void KCoreDirLister::updateDirectory(const QUrl &dirUrl) 2313 { 2314 kDirListerCache()->updateDirectory(dirUrl); 2315 } 2316 2317 bool KCoreDirLister::isFinished() const 2318 { 2319 return d->complete; 2320 } 2321 2322 KFileItem KCoreDirLister::rootItem() const 2323 { 2324 return d->rootFileItem; 2325 } 2326 2327 KFileItem KCoreDirLister::findByUrl(const QUrl &url) const 2328 { 2329 return kDirListerCache()->findByUrl(this, url); 2330 } 2331 2332 KFileItem KCoreDirLister::findByName(const QString &name) const 2333 { 2334 return kDirListerCache()->findByName(this, name); 2335 } 2336 2337 // ================ public filter methods ================ // 2338 2339 void KCoreDirLister::setNameFilter(const QString &nameFilter) 2340 { 2341 if (d->nameFilter == nameFilter) { 2342 return; 2343 } 2344 2345 d->prepareForSettingsChange(); 2346 2347 d->settings.lstFilters.clear(); 2348 d->nameFilter = nameFilter; 2349 // Split on white space 2350 const QStringList list = nameFilter.split(QLatin1Char(' '), Qt::SkipEmptyParts); 2351 for (const QString &filter : list) { 2352 d->settings.lstFilters.append(QRegularExpression(QRegularExpression::wildcardToRegularExpression(filter), QRegularExpression::CaseInsensitiveOption)); 2353 } 2354 } 2355 2356 QString KCoreDirLister::nameFilter() const 2357 { 2358 return d->nameFilter; 2359 } 2360 2361 void KCoreDirLister::setMimeFilter(const QStringList &mimeFilter) 2362 { 2363 if (d->settings.mimeFilter == mimeFilter) { 2364 return; 2365 } 2366 2367 d->prepareForSettingsChange(); 2368 if (mimeFilter.contains(QLatin1String("application/octet-stream")) || mimeFilter.contains(QLatin1String("all/allfiles"))) { // all files 2369 d->settings.mimeFilter.clear(); 2370 } else { 2371 d->settings.mimeFilter = mimeFilter; 2372 } 2373 } 2374 2375 void KCoreDirLister::setMimeExcludeFilter(const QStringList &mimeExcludeFilter) 2376 { 2377 if (d->settings.mimeExcludeFilter == mimeExcludeFilter) { 2378 return; 2379 } 2380 2381 d->prepareForSettingsChange(); 2382 d->settings.mimeExcludeFilter = mimeExcludeFilter; 2383 } 2384 2385 void KCoreDirLister::clearMimeFilter() 2386 { 2387 d->prepareForSettingsChange(); 2388 d->settings.mimeFilter.clear(); 2389 d->settings.mimeExcludeFilter.clear(); 2390 } 2391 2392 QStringList KCoreDirLister::mimeFilters() const 2393 { 2394 return d->settings.mimeFilter; 2395 } 2396 2397 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 94) 2398 bool KCoreDirLister::matchesFilter(const QString &name) const 2399 { 2400 return d->matchesFilter(name); 2401 } 2402 #endif 2403 2404 bool KCoreDirListerPrivate::matchesFilter(const QString &name) const 2405 { 2406 return std::any_of(settings.lstFilters.cbegin(), settings.lstFilters.cend(), [&name](const QRegularExpression &filter) { 2407 return filter.match(name).hasMatch(); 2408 }); 2409 } 2410 2411 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 94) 2412 bool KCoreDirLister::matchesMimeFilter(const QString &mime) const 2413 { 2414 return d->matchesMimeFilter(mime); 2415 } 2416 #endif 2417 2418 bool KCoreDirListerPrivate::matchesMimeFilter(const QString &mime) const 2419 { 2420 return q->doMimeFilter(mime, settings.mimeFilter) && doMimeExcludeFilter(mime, settings.mimeExcludeFilter); 2421 } 2422 2423 // ================ protected methods ================ // 2424 2425 bool KCoreDirLister::matchesFilter(const KFileItem &item) const 2426 { 2427 Q_ASSERT(!item.isNull()); 2428 2429 if (item.text() == QLatin1String("..")) { 2430 return false; 2431 } 2432 2433 if (!d->settings.isShowingDotFiles && item.isHidden()) { 2434 return false; 2435 } 2436 2437 if (item.isDir() || d->settings.lstFilters.isEmpty()) { 2438 return true; 2439 } 2440 2441 return d->matchesFilter(item.text()); 2442 } 2443 2444 bool KCoreDirLister::matchesMimeFilter(const KFileItem &item) const 2445 { 2446 Q_ASSERT(!item.isNull()); 2447 // Don't lose time determining the MIME type if there is no filter 2448 if (d->settings.mimeFilter.isEmpty() && d->settings.mimeExcludeFilter.isEmpty()) { 2449 return true; 2450 } 2451 return d->matchesMimeFilter(item.mimetype()); 2452 } 2453 2454 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 90) 2455 bool KCoreDirLister::doNameFilter(const QString &name, const QList<QRegExp> &filters) const 2456 { 2457 return std::any_of(filters.cbegin(), filters.cend(), [&name](const QRegExp &filter) { 2458 return filter.exactMatch(name); 2459 }); 2460 } 2461 #endif 2462 2463 bool KCoreDirLister::doMimeFilter(const QString &mime, const QStringList &filters) const 2464 { 2465 if (filters.isEmpty()) { 2466 return true; 2467 } 2468 2469 QMimeDatabase db; 2470 const QMimeType mimeptr = db.mimeTypeForName(mime); 2471 if (!mimeptr.isValid()) { 2472 return false; 2473 } 2474 2475 qCDebug(KIO_CORE_DIRLISTER) << "doMimeFilter: investigating:" << mimeptr.name(); 2476 return std::any_of(filters.cbegin(), filters.cend(), [&mimeptr](const QString &filter) { 2477 return mimeptr.inherits(filter); 2478 }); 2479 } 2480 2481 bool KCoreDirListerPrivate::doMimeExcludeFilter(const QString &mime, const QStringList &filters) const 2482 { 2483 return !std::any_of(filters.cbegin(), filters.cend(), [&mime](const QString &filter) { 2484 return mime == filter; 2485 }); 2486 } 2487 2488 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 82) 2489 void KCoreDirLister::handleError(KIO::Job *job) 2490 { 2491 qCWarning(KIO_CORE) << job->errorString(); 2492 } 2493 #endif 2494 2495 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 81) 2496 void KCoreDirLister::handleErrorMessage(const QString &message) 2497 { 2498 qCWarning(KIO_CORE) << message; 2499 } 2500 #endif 2501 2502 // ================= private methods ================= // 2503 2504 void KCoreDirListerPrivate::addNewItem(const QUrl &directoryUrl, const KFileItem &item) 2505 { 2506 if (!isItemVisible(item)) { 2507 return; // No reason to continue... bailing out here prevents a MIME type scan. 2508 } 2509 2510 qCDebug(KIO_CORE_DIRLISTER) << "in" << directoryUrl << "item:" << item.url(); 2511 2512 if (q->matchesMimeFilter(item)) { 2513 Q_ASSERT(!item.isNull()); 2514 lstNewItems[directoryUrl].append(item); // items not filtered 2515 } else { 2516 Q_ASSERT(!item.isNull()); 2517 lstMimeFilteredItems.append(item); // only filtered by MIME type 2518 } 2519 } 2520 2521 void KCoreDirListerPrivate::addNewItems(const QUrl &directoryUrl, const QList<KFileItem> &items) 2522 { 2523 // TODO: make this faster - test if we have a filter at all first 2524 // DF: was this profiled? The matchesFoo() functions should be fast, w/o filters... 2525 // Of course if there is no filter and we can do a range-insertion instead of a loop, that might be good. 2526 for (const auto &item : items) { 2527 addNewItem(directoryUrl, item); 2528 } 2529 } 2530 2531 void KCoreDirListerPrivate::addRefreshItem(const QUrl &directoryUrl, const KFileItem &oldItem, const KFileItem &item) 2532 { 2533 // Refreshing the root item "." of a dirlister 2534 if (directoryUrl == item.url()) { 2535 lstRefreshItems.append({oldItem, item}); 2536 return; 2537 } 2538 2539 const bool refreshItemWasFiltered = !isItemVisible(oldItem) || !q->matchesMimeFilter(oldItem); 2540 if (isItemVisible(item) && q->matchesMimeFilter(item)) { 2541 if (refreshItemWasFiltered) { 2542 Q_ASSERT(!item.isNull()); 2543 lstNewItems[directoryUrl].append(item); 2544 } else { 2545 Q_ASSERT(!item.isNull()); 2546 lstRefreshItems.append(qMakePair(oldItem, item)); 2547 } 2548 } else if (!refreshItemWasFiltered) { 2549 // notify the user that the MIME type of a file changed that doesn't match 2550 // a filter or does match an exclude filter 2551 // This also happens when renaming foo to .foo and dot files are hidden (#174721) 2552 Q_ASSERT(!oldItem.isNull()); 2553 lstRemoveItems.append(oldItem); 2554 } 2555 } 2556 2557 void KCoreDirListerPrivate::emitItems() 2558 { 2559 if (!lstNewItems.empty()) { 2560 for (auto it = lstNewItems.cbegin(); it != lstNewItems.cend(); ++it) { 2561 const auto &val = it.value(); 2562 Q_EMIT q->itemsAdded(it.key(), val); 2563 Q_EMIT q->newItems(val); // compat 2564 } 2565 lstNewItems.clear(); 2566 } 2567 2568 if (!lstMimeFilteredItems.empty()) { 2569 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100) 2570 Q_EMIT q->itemsFilteredByMime(lstMimeFilteredItems); 2571 #endif 2572 lstMimeFilteredItems.clear(); 2573 } 2574 2575 if (!lstRefreshItems.empty()) { 2576 Q_EMIT q->refreshItems(lstRefreshItems); 2577 lstRefreshItems.clear(); 2578 } 2579 2580 if (!lstRemoveItems.empty()) { 2581 Q_EMIT q->itemsDeleted(lstRemoveItems); 2582 lstRemoveItems.clear(); 2583 } 2584 } 2585 2586 bool KCoreDirListerPrivate::isItemVisible(const KFileItem &item) const 2587 { 2588 // Note that this doesn't include MIME type filters, because 2589 // of the itemsFilteredByMime signal. Filtered-by-MIME-type items are 2590 // considered "visible", they are just visible via a different signal... 2591 return (!settings.dirOnlyMode || item.isDir()) && q->matchesFilter(item); 2592 } 2593 2594 void KCoreDirListerPrivate::emitItemsDeleted(const KFileItemList &itemsList) 2595 { 2596 KFileItemList items; 2597 std::copy_if(itemsList.cbegin(), itemsList.cend(), std::back_inserter(items), [this](const KFileItem &item) { 2598 return isItemVisible(item) || q->matchesMimeFilter(item); 2599 }); 2600 if (!items.isEmpty()) { 2601 Q_EMIT q->itemsDeleted(items); 2602 } 2603 } 2604 2605 KCoreDirListerPrivate::KCoreDirListerPrivate(KCoreDirLister *qq) 2606 : q(qq) 2607 { 2608 } 2609 2610 // ================ private slots ================ // 2611 2612 void KCoreDirListerPrivate::slotInfoMessage(KJob *, const QString &message) 2613 { 2614 Q_EMIT q->infoMessage(message); 2615 } 2616 2617 void KCoreDirListerPrivate::slotPercent(KJob *job, unsigned long pcnt) 2618 { 2619 jobData[static_cast<KIO::ListJob *>(job)].percent = pcnt; 2620 2621 int result = 0; 2622 2623 KIO::filesize_t size = 0; 2624 2625 for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) { 2626 const JobData &data = dataIt.value(); 2627 result += data.percent * data.totalSize; 2628 size += data.totalSize; 2629 } 2630 2631 if (size != 0) { 2632 result /= size; 2633 } else { 2634 result = 100; 2635 } 2636 Q_EMIT q->percent(result); 2637 } 2638 2639 void KCoreDirListerPrivate::slotTotalSize(KJob *job, qulonglong size) 2640 { 2641 jobData[static_cast<KIO::ListJob *>(job)].totalSize = size; 2642 2643 KIO::filesize_t result = 0; 2644 for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) { 2645 result += dataIt.value().totalSize; 2646 } 2647 2648 Q_EMIT q->totalSize(result); 2649 } 2650 2651 void KCoreDirListerPrivate::slotProcessedSize(KJob *job, qulonglong size) 2652 { 2653 jobData[static_cast<KIO::ListJob *>(job)].processedSize = size; 2654 2655 KIO::filesize_t result = 0; 2656 for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) { 2657 result += dataIt.value().processedSize; 2658 } 2659 2660 Q_EMIT q->processedSize(result); 2661 } 2662 2663 void KCoreDirListerPrivate::slotSpeed(KJob *job, unsigned long spd) 2664 { 2665 jobData[static_cast<KIO::ListJob *>(job)].speed = spd; 2666 2667 int result = 0; 2668 for (auto dataIt = jobData.cbegin(); dataIt != jobData.cend(); ++dataIt) { 2669 result += dataIt.value().speed; 2670 } 2671 2672 Q_EMIT q->speed(result); 2673 } 2674 2675 uint KCoreDirListerPrivate::numJobs() 2676 { 2677 #ifdef DEBUG_CACHE 2678 // This code helps detecting stale entries in the jobData map. 2679 qCDebug(KIO_CORE_DIRLISTER) << q << "numJobs:" << jobData.count(); 2680 for (auto it = jobData.cbegin(); it != jobData.cend(); ++it) { 2681 qCDebug(KIO_CORE_DIRLISTER) << (void *)it.key(); 2682 qCDebug(KIO_CORE_DIRLISTER) << it.key(); 2683 } 2684 #endif 2685 2686 return jobData.count(); 2687 } 2688 2689 void KCoreDirListerPrivate::jobDone(KIO::ListJob *job) 2690 { 2691 jobData.remove(job); 2692 } 2693 2694 void KCoreDirLister::jobStarted(KIO::ListJob *job) 2695 { 2696 KCoreDirListerPrivate::JobData data; 2697 data.speed = 0; 2698 data.percent = 0; 2699 data.processedSize = 0; 2700 data.totalSize = 0; 2701 2702 d->jobData.insert(job, data); 2703 d->complete = false; 2704 } 2705 2706 void KCoreDirListerPrivate::connectJob(KIO::ListJob *job) 2707 { 2708 q->connect(job, &KJob::infoMessage, q, [this](KJob *job, const QString &plain) { 2709 slotInfoMessage(job, plain); 2710 }); 2711 2712 q->connect(job, &KJob::percentChanged, q, [this](KJob *job, ulong _percent) { 2713 slotPercent(job, _percent); 2714 }); 2715 2716 q->connect(job, &KJob::totalSize, q, [this](KJob *job, qulonglong _size) { 2717 slotTotalSize(job, _size); 2718 }); 2719 q->connect(job, &KJob::processedSize, q, [this](KJob *job, qulonglong _psize) { 2720 slotProcessedSize(job, _psize); 2721 }); 2722 q->connect(job, &KJob::speed, q, [this](KJob *job, qulonglong _speed) { 2723 slotSpeed(job, _speed); 2724 }); 2725 } 2726 2727 KFileItemList KCoreDirLister::items(WhichItems which) const 2728 { 2729 return itemsForDir(url(), which); 2730 } 2731 2732 KFileItemList KCoreDirLister::itemsForDir(const QUrl &dir, WhichItems which) const 2733 { 2734 QList<KFileItem> *allItems = kDirListerCache()->itemsForDir(dir); 2735 KFileItemList result; 2736 if (!allItems) { 2737 return result; 2738 } 2739 2740 if (which == AllItems) { 2741 return KFileItemList(*allItems); 2742 } else { // only items passing the filters 2743 std::copy_if(allItems->cbegin(), allItems->cend(), std::back_inserter(result), [this](const KFileItem &item) { 2744 return d->isItemVisible(item) && matchesMimeFilter(item); 2745 }); 2746 } 2747 return result; 2748 } 2749 2750 bool KCoreDirLister::delayedMimeTypes() const 2751 { 2752 return d->delayedMimeTypes; 2753 } 2754 2755 void KCoreDirLister::setDelayedMimeTypes(bool delayedMimeTypes) 2756 { 2757 d->delayedMimeTypes = delayedMimeTypes; 2758 } 2759 2760 // called by KCoreDirListerCache::slotRedirection 2761 void KCoreDirListerPrivate::redirect(const QUrl &oldUrl, const QUrl &newUrl, bool keepItems) 2762 { 2763 if (url.matches(oldUrl, QUrl::StripTrailingSlash)) { 2764 if (!keepItems) { 2765 rootFileItem = KFileItem(); 2766 } else { 2767 rootFileItem.setUrl(newUrl); 2768 } 2769 url = newUrl; 2770 } 2771 2772 const int idx = lstDirs.indexOf(oldUrl); 2773 if (idx == -1) { 2774 qCWarning(KIO_CORE) << "Unexpected redirection from" << oldUrl << "to" << newUrl << "but this dirlister is currently listing/holding" << lstDirs; 2775 } else { 2776 lstDirs[idx] = newUrl; 2777 } 2778 2779 if (lstDirs.count() == 1) { 2780 if (!keepItems) { 2781 Q_EMIT q->clear(); 2782 } 2783 2784 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 80) 2785 Q_EMIT q->redirection(newUrl); 2786 #endif 2787 2788 } else { 2789 if (!keepItems) { 2790 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 2791 Q_EMIT q->clear(oldUrl); 2792 #endif 2793 Q_EMIT q->clearDir(oldUrl); 2794 } 2795 } 2796 Q_EMIT q->redirection(oldUrl, newUrl); 2797 } 2798 2799 void KCoreDirListerCacheDirectoryData::moveListersWithoutCachedItemsJob(const QUrl &url) 2800 { 2801 // Move dirlisters from listersCurrentlyListing to listersCurrentlyHolding, 2802 // but not those that are still waiting on a CachedItemsJob... 2803 // Unit-testing note: 2804 // Run kdirmodeltest in valgrind to hit the case where an update 2805 // is triggered while a lister has a CachedItemsJob (different timing...) 2806 QMutableListIterator<KCoreDirLister *> lister_it(listersCurrentlyListing); 2807 while (lister_it.hasNext()) { 2808 KCoreDirLister *kdl = lister_it.next(); 2809 if (!kdl->d->cachedItemsJobForUrl(url)) { 2810 // OK, move this lister from "currently listing" to "currently holding". 2811 2812 // Huh? The KCoreDirLister was present twice in listersCurrentlyListing, or was in both lists? 2813 Q_ASSERT(!listersCurrentlyHolding.contains(kdl)); 2814 if (!listersCurrentlyHolding.contains(kdl)) { 2815 listersCurrentlyHolding.append(kdl); 2816 } 2817 lister_it.remove(); 2818 } else { 2819 qCDebug(KIO_CORE_DIRLISTER) << "Not moving" << kdl << "to listersCurrentlyHolding because it still has job" << kdl->d->m_cachedItemsJobs; 2820 } 2821 } 2822 } 2823 2824 KFileItem KCoreDirLister::cachedItemForUrl(const QUrl &url) 2825 { 2826 if (kDirListerCache.exists()) { 2827 return kDirListerCache()->itemForUrl(url); 2828 } else { 2829 return {}; 2830 } 2831 } 2832 2833 bool KCoreDirLister::autoErrorHandlingEnabled() const 2834 { 2835 return d->m_autoErrorHandling; 2836 } 2837 2838 void KCoreDirLister::setAutoErrorHandlingEnabled(bool enable) 2839 { 2840 d->m_autoErrorHandling = enable; 2841 } 2842 2843 KCoreDirListerCache::CacheHiddenFile *KCoreDirListerCache::cachedDotHiddenForDir(const QString &dir) 2844 { 2845 const QString path = dir + QLatin1String("/.hidden"); 2846 QFile dotHiddenFile(path); 2847 2848 if (dotHiddenFile.exists()) { 2849 const QDateTime mtime = QFileInfo(dotHiddenFile).lastModified(); 2850 CacheHiddenFile *cachedDotHiddenFile = m_cacheHiddenFiles.object(path); 2851 2852 if (cachedDotHiddenFile && mtime <= cachedDotHiddenFile->mtime) { 2853 // ".hidden" is in cache and still valid (the file was not modified since then), 2854 // so return it 2855 return cachedDotHiddenFile; 2856 } else { 2857 // read the ".hidden" file, then cache it and return it 2858 if (dotHiddenFile.open(QIODevice::ReadOnly | QIODevice::Text)) { 2859 std::set<QString> filesToHide; 2860 QTextStream stream(&dotHiddenFile); 2861 while (!stream.atEnd()) { 2862 QString name = stream.readLine(); 2863 if (!name.isEmpty()) { 2864 filesToHide.insert(name); 2865 } 2866 } 2867 2868 m_cacheHiddenFiles.insert(path, new CacheHiddenFile(mtime, std::move(filesToHide))); 2869 2870 return m_cacheHiddenFiles.object(path); 2871 } 2872 } 2873 } 2874 2875 return {}; 2876 } 2877 2878 #include "moc_kcoredirlister.cpp" 2879 #include "moc_kcoredirlister_p.cpp"