/* * 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(this, &KDirectoryContentsCounter::stop, m_worker, &KDirectoryContentsCounterWorker::stop); 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::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(); scanDirectory(firstPath, PathCountPriority::High); } else if (!m_queue.empty()) { const QString firstPath = m_queue.front(); m_queue.pop_front(); scanDirectory(firstPath, PathCountPriority::Normal); } 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; } scanDirectory(path, PathCountPriority::High); } } 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::scanDirectory(const QString &path, PathCountPriority priority) { 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) { // only enqueue path not yet in queue 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 (priority == PathCountPriority::Normal) { if (alreadyInCache) { // if we already knew the dir size, it gets lower priority m_queue.push_back(path); } else { m_queue.push_front(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; } } void KDirectoryContentsCounter::stopWorker() { m_queue.clear(); m_priorityQueue.clear(); Q_EMIT stop(); } QThread *KDirectoryContentsCounter::m_workerThread = nullptr;