/* * SPDX-FileCopyrightText: 2011 Peter Penz * SPDX-FileCopyrightText: 2013 Frank Reininghaus * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "kdirectorycontentscounter.h" #include "kitemviews/kfileitemmodel.h" #include #include #include #include namespace { /// cache of directory counting result static QHash> *s_cache; } KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel *model, QObject *parent) : QObject(parent) , m_model(model) , m_queue() , m_worker(nullptr) , m_workerIsBusy(false) , m_dirWatcher(nullptr) , m_watchedDirs() { connect(m_model, &KFileItemModel::itemsRemoved, this, &KDirectoryContentsCounter::slotItemsRemoved); if (!m_workerThread) { m_workerThread = new QThread(); m_workerThread->start(); } if (s_cache == nullptr) { s_cache = new QHash>(); } m_worker = new KDirectoryContentsCounterWorker(); m_worker->moveToThread(m_workerThread); connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, m_worker, &KDirectoryContentsCounterWorker::countDirectoryContents); connect(m_worker, &KDirectoryContentsCounterWorker::result, this, &KDirectoryContentsCounter::slotResult); m_dirWatcher = new KDirWatch(this); connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty); } KDirectoryContentsCounter::~KDirectoryContentsCounter() { if (m_workerThread->isRunning()) { // 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 = nullptr; // 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 KDirectoryContentsCounter::scanDirectory(const QString &path) { startWorker(path); } void KDirectoryContentsCounter::slotResult(const QString &path, int count, long size) { m_workerIsBusy = false; const QFileInfo info = QFileInfo(path); const QString resolvedPath = info.canonicalFilePath(); if (!m_dirWatcher->contains(resolvedPath)) { m_dirWatcher->addDir(resolvedPath); m_watchedDirs.insert(resolvedPath); } if (!m_priorityQueue.empty()) { const QString firstPath = m_priorityQueue.front(); m_priorityQueue.pop_front(); startWorker(firstPath); } else if (!m_queue.empty()) { const QString firstPath = m_queue.front(); m_queue.pop_front(); startWorker(firstPath); } if (s_cache->contains(resolvedPath)) { const auto pair = s_cache->value(resolvedPath); if (pair.first == count && pair.second == size) { // no change no need to send another result event return; } } if (info.dir().path() == m_model->rootItem().url().path()) { // update cache or overwrite value // when path is a direct children of the current model root s_cache->insert(resolvedPath, QPair(count, size)); } // sends the results Q_EMIT result(path, count, size); } void KDirectoryContentsCounter::slotDirWatchDirty(const QString &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 // also for changed files inside the directory, even if we // don't enable this behavior explicitly (see bug 309740). return; } startWorker(path); } } void KDirectoryContentsCounter::slotItemsRemoved() { const bool allItemsRemoved = (m_model->count() == 0); if (!m_watchedDirs.isEmpty()) { // Don't let KDirWatch watch for removed items if (allItemsRemoved) { for (const QString &path : qAsConst(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(QUrl::fromLocalFile(path)) < 0) { m_dirWatcher->removeDir(path); it.remove(); } } } } } void KDirectoryContentsCounter::startWorker(const QString &path) { const QString resolvedPath = QFileInfo(path).canonicalFilePath(); const bool alreadyInCache = s_cache->contains(resolvedPath); if (alreadyInCache) { // fast path when in cache // will be updated later if result has changed const auto pair = s_cache->value(resolvedPath); Q_EMIT result(path, pair.first, pair.second); } if (m_workerIsBusy) { if (std::find(m_queue.begin(), m_queue.end(), path) == m_queue.end() && std::find(m_priorityQueue.begin(), m_priorityQueue.end(), path) == m_priorityQueue.end()) { if (alreadyInCache) { m_queue.push_back(path); } else { // append to priority queue m_priorityQueue.push_back(path); } } } else { KDirectoryContentsCounterWorker::Options options; if (m_model->showHiddenFiles()) { options |= KDirectoryContentsCounterWorker::CountHiddenFiles; } if (m_model->showDirectoriesOnly()) { options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly; } Q_EMIT requestDirectoryContentsCount(path, options); m_workerIsBusy = true; } } QThread *KDirectoryContentsCounter::m_workerThread = nullptr;