X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/de3e2ae40f626c1368dfd40bace54ef3e7815833..dd07a327:/src/kitemviews/private/kdirectorycontentscounter.cpp diff --git a/src/kitemviews/private/kdirectorycontentscounter.cpp b/src/kitemviews/private/kdirectorycontentscounter.cpp index cd448e233..dcf7247a9 100644 --- a/src/kitemviews/private/kdirectorycontentscounter.cpp +++ b/src/kitemviews/private/kdirectorycontentscounter.cpp @@ -1,128 +1,147 @@ -/*************************************************************************** - * Copyright (C) 2011 by Peter Penz * - * Copyright (C) 2013 by Frank Reininghaus * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * - ***************************************************************************/ +/* + * SPDX-FileCopyrightText: 2011 Peter Penz + * SPDX-FileCopyrightText: 2013 Frank Reininghaus + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "kdirectorycontentscounter.h" - -#include "kdirectorycontentscounterworker.h" -#include +#include "dolphin_contentdisplaysettings.h" +#include "kitemviews/kfileitemmodel.h" #include + +#include +#include #include -KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel* model, QObject* parent) : - QObject(parent), - m_model(model), - m_queue(), - m_worker(0), - m_workerIsBusy(false), - m_dirWatcher(0), - m_watchedDirs() +namespace { - connect(m_model, &KFileItemModel::itemsRemoved, - this, &KDirectoryContentsCounter::slotItemsRemoved); - if (!m_workerThread) { - m_workerThread = new QThread(); - m_workerThread->start(); - } +class LocalCache +{ +public: + struct cacheData { + int count = 0; + long long size = 0; + ushort refCount = 0; + qint64 timestamp = 0; - m_worker = new KDirectoryContentsCounterWorker(); - m_worker->moveToThread(m_workerThread); - ++m_workersCount; + inline operator bool() const + { + return timestamp != 0 && count != -1; + } + }; - connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, - m_worker, &KDirectoryContentsCounterWorker::countDirectoryContents); - connect(m_worker, &KDirectoryContentsCounterWorker::result, - this, &KDirectoryContentsCounter::slotResult); + LocalCache() + : m_cache() + { + } - m_dirWatcher = new KDirWatch(this); - connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty); -} + cacheData insert(const QString &key, cacheData data, bool inserted) + { + data.timestamp = QDateTime::currentMSecsSinceEpoch(); + if (inserted) { + data.refCount += 1; + } + m_cache.insert(key, data); + return data; + } -KDirectoryContentsCounter::~KDirectoryContentsCounter() -{ - --m_workersCount; + cacheData value(const QString &key) const + { + return m_cache.value(key); + } - if (m_workersCount > 0) { - // The worker thread will continue running. It could even be running - // a method of m_worker at the moment, so we delete it using - // deleteLater() to prevent a crash. - m_worker->deleteLater(); - } else { - // There are no remaining workers -> stop the worker thread. - m_workerThread->quit(); - m_workerThread->wait(); - delete m_workerThread; - m_workerThread = 0; + void unRefAll(const QSet &keys) + { + for (const auto &key : keys) { + auto entry = m_cache[key]; + entry.refCount -= 1; + if (entry.refCount == 0) { + m_cache.remove(key); + } + } + } - // The worker thread has finished running now, so it's safe to delete - // m_worker. deleteLater() would not work at all because the event loop - // which would deliver the event to m_worker is not running any more. - delete m_worker; + void removeAll(const QSet &keys) + { + for (const auto &key : keys) { + m_cache.remove(key); + } } -} -void KDirectoryContentsCounter::addDirectory(const QString& path) -{ - startWorker(path); +private: + QHash m_cache; +}; + +/// cache of directory counting result +static LocalCache *s_cache; +static QThread *s_workerThread; +static KDirectoryContentsCounterWorker *s_worker; } -int KDirectoryContentsCounter::countDirectoryContentsSynchronously(const QString& path) +KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel *model, QObject *parent) + : QObject(parent) + , m_model(model) + , m_priorityQueue() + , m_queue() + , m_workerIsBusy(false) + , m_dirWatcher(nullptr) + , m_watchedDirs() { - if (!m_dirWatcher->contains(path)) { - m_dirWatcher->addDir(path); - m_watchedDirs.insert(path); + if (s_cache == nullptr) { + s_cache = new LocalCache(); } - KDirectoryContentsCounterWorker::Options options; - - if (m_model->showHiddenFiles()) { - options |= KDirectoryContentsCounterWorker::CountHiddenFiles; + if (!s_workerThread) { + s_workerThread = new QThread(); + s_workerThread->setObjectName(QStringLiteral("KDirectoryContentsCounterThread")); + s_workerThread->start(); } - if (m_model->showDirectoriesOnly()) { - options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly; + if (!s_worker) { + s_worker = new KDirectoryContentsCounterWorker(); + s_worker->moveToThread(s_workerThread); } - return KDirectoryContentsCounterWorker::subItemsCount(path, options); + connect(m_model, &KFileItemModel::itemsRemoved, this, &KDirectoryContentsCounter::slotItemsRemoved); + connect(m_model, &KFileItemModel::directoryRefreshing, this, &KDirectoryContentsCounter::slotDirectoryRefreshing); + + connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, s_worker, &KDirectoryContentsCounterWorker::countDirectoryContents); + + connect(s_worker, &KDirectoryContentsCounterWorker::result, this, &KDirectoryContentsCounter::slotResult); + connect(s_worker, &KDirectoryContentsCounterWorker::intermediateResult, this, &KDirectoryContentsCounter::result); + connect(s_worker, &KDirectoryContentsCounterWorker::finished, this, &KDirectoryContentsCounter::scheduleNext); + + m_dirWatcher = new KDirWatch(this); + connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty); } -void KDirectoryContentsCounter::slotResult(const QString& path, int count) +KDirectoryContentsCounter::~KDirectoryContentsCounter() { - m_workerIsBusy = false; + s_cache->unRefAll(m_watchedDirs); +} - if (!m_dirWatcher->contains(path)) { - m_dirWatcher->addDir(path); - m_watchedDirs.insert(path); +void KDirectoryContentsCounter::slotResult(const QString &path, int count, long long size) +{ + const auto fileInfo = QFileInfo(path); + const QString resolvedPath = fileInfo.canonicalFilePath(); + if (fileInfo.isReadable() && !m_watchedDirs.contains(resolvedPath)) { + m_dirWatcher->addDir(resolvedPath); } + bool inserted = m_watchedDirs.insert(resolvedPath) == m_watchedDirs.end(); - if (!m_queue.isEmpty()) { - startWorker(m_queue.dequeue()); - } + // update cache or overwrite value + s_cache->insert(resolvedPath, {count, size, true}, inserted); - emit result(path, count); + // sends the results + Q_EMIT result(path, count, size); } -void KDirectoryContentsCounter::slotDirWatchDirty(const QString& path) +void KDirectoryContentsCounter::slotDirWatchDirty(const QString &path) { - const int index = m_model->index(KUrl(path)); + const int index = m_model->index(QUrl::fromLocalFile(path)); if (index >= 0) { if (!m_model->fileItem(index).isDir()) { // If INotify is used, KDirWatch issues the dirty() signal @@ -131,7 +150,7 @@ void KDirectoryContentsCounter::slotDirWatchDirty(const QString& path) return; } - startWorker(path); + scanDirectory(path, PathCountPriority::High); } } @@ -139,19 +158,23 @@ void KDirectoryContentsCounter::slotItemsRemoved() { const bool allItemsRemoved = (m_model->count() == 0); + if (allItemsRemoved) { + s_cache->removeAll(m_watchedDirs); + stopWorker(); + } + if (!m_watchedDirs.isEmpty()) { // Don't let KDirWatch watch for removed items if (allItemsRemoved) { - foreach (const QString& path, m_watchedDirs) { + for (const QString &path : std::as_const(m_watchedDirs)) { m_dirWatcher->removeDir(path); } m_watchedDirs.clear(); - m_queue.clear(); } else { QMutableSetIterator it(m_watchedDirs); while (it.hasNext()) { - const QString& path = it.next(); - if (m_model->index(KUrl(path)) < 0) { + const QString &path = it.next(); + if (m_model->index(QUrl::fromLocalFile(path)) < 0) { m_dirWatcher->removeDir(path); it.remove(); } @@ -160,25 +183,114 @@ void KDirectoryContentsCounter::slotItemsRemoved() } } -void KDirectoryContentsCounter::startWorker(const QString& path) +void KDirectoryContentsCounter::slotDirectoryRefreshing() { - if (m_workerIsBusy) { - m_queue.enqueue(path); + s_cache->removeAll(m_watchedDirs); +} + +void KDirectoryContentsCounter::scheduleNext() +{ + if (!m_priorityQueue.empty()) { + m_currentPath = m_priorityQueue.front(); + m_priorityQueue.pop_front(); + } else if (!m_queue.empty()) { + m_currentPath = m_queue.front(); + m_queue.pop_front(); } else { - KDirectoryContentsCounterWorker::Options options; + m_currentPath.clear(); + m_workerIsBusy = false; + return; + } + + const auto fileInfo = QFileInfo(m_currentPath); + const QString resolvedPath = fileInfo.canonicalFilePath(); + const auto pair = s_cache->value(resolvedPath); + if (pair) { + // fast path when in cache + // will be updated later if result has changed + Q_EMIT result(m_currentPath, pair.count, pair.size); + } + + // if scanned fully recently, skip rescan + if (pair && pair.timestamp >= fileInfo.fileTime(QFile::FileModificationTime).toMSecsSinceEpoch()) { + scheduleNext(); + return; + } + + KDirectoryContentsCounterWorker::Options options; + + if (m_model->showHiddenFiles()) { + options |= KDirectoryContentsCounterWorker::CountHiddenFiles; + } - if (m_model->showHiddenFiles()) { - options |= KDirectoryContentsCounterWorker::CountHiddenFiles; + m_workerIsBusy = true; + Q_EMIT requestDirectoryContentsCount(m_currentPath, options, ContentDisplaySettings::recursiveDirectorySizeLimit()); +} + +void KDirectoryContentsCounter::enqueuePathScanning(const QString &path, bool alreadyInCache, PathCountPriority priority) +{ + // ensure to update the entry in the queue + auto it = std::find(m_queue.begin(), m_queue.end(), path); + if (it != m_queue.end()) { + m_queue.erase(it); + } else { + it = std::find(m_priorityQueue.begin(), m_priorityQueue.end(), path); + if (it != m_priorityQueue.end()) { + m_priorityQueue.erase(it); } + } - if (m_model->showDirectoriesOnly()) { - options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly; + if (priority == PathCountPriority::Normal) { + if (alreadyInCache) { + // we already knew the dir size + // otherwise it gets lower priority + m_queue.push_back(path); + } else { + m_queue.push_front(path); } + } else { + // append to priority queue + m_priorityQueue.push_front(path); + } +} + +void KDirectoryContentsCounter::scanDirectory(const QString &path, PathCountPriority priority) +{ + if (m_workerIsBusy && m_currentPath == path) { + // already listing + return; + } + + const auto fileInfo = QFileInfo(path); + const QString resolvedPath = fileInfo.canonicalFilePath(); + const auto pair = s_cache->value(resolvedPath); + if (pair) { + // fast path when in cache + // will be updated later if result has changed + Q_EMIT result(path, pair.count, pair.size); + } + + // if scanned fully recently, skip rescan + if (pair && pair.timestamp >= fileInfo.fileTime(QFile::FileModificationTime).toMSecsSinceEpoch()) { + return; + } + + enqueuePathScanning(path, pair, priority); + + if (!m_workerIsBusy && !s_worker->stopping()) { + scheduleNext(); + } +} + +void KDirectoryContentsCounter::stopWorker() +{ + m_queue.clear(); + m_priorityQueue.clear(); - emit requestDirectoryContentsCount(path, options); - m_workerIsBusy = true; + if (m_workerIsBusy && m_currentPath == s_worker->scannedPath()) { + s_worker->stop(); } + m_currentPath.clear(); } -QThread* KDirectoryContentsCounter::m_workerThread = 0; -int KDirectoryContentsCounter::m_workersCount = 0; \ No newline at end of file +#include "moc_kdirectorycontentscounter.cpp"