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