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