]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/private/kdirectorycontentscounter.cpp
Add explicit moc includes to sources for moc-covered headers
[dolphin.git] / src / kitemviews / private / kdirectorycontentscounter.cpp
index a0ed8c27cadc0320dd3ec4b5d32f46b405a67a8a..47f538447b503cb02c12259a741354a5e9a7ffef 100644 (file)
-/***************************************************************************
- *   Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com>             *
- *   Copyright (C) 2013 by Frank Reininghaus <frank78ac@googlemail.com>    *
- *                                                                         *
- *   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 <peter.penz19@gmail.com>
+ * SPDX-FileCopyrightText: 2013 Frank Reininghaus <frank78ac@googlemail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
 
 #include "kdirectorycontentscounter.h"
+#include "dolphin_contentdisplaysettings.h"
 #include "kitemviews/kfileitemmodel.h"
 
 #include <KDirWatch>
 
-#include <QFileInfo>
 #include <QDir>
+#include <QFileInfo>
 #include <QThread>
 
-namespace  {
-    /// cache of directory counting result
-    static QHash<QString, QPair<int, long>> *s_cache;
-}
+namespace
+{
 
-KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel* model, QObject* parent) :
-    QObject(parent),
-    m_model(model),
-    m_queue(),
-    m_worker(nullptr),
-    m_workerIsBusy(false),
-    m_dirWatcher(nullptr),
-    m_watchedDirs()
+class LocalCache
 {
-    connect(m_model, &KFileItemModel::itemsRemoved,
-            this,    &KDirectoryContentsCounter::slotItemsRemoved);
+public:
+    struct cacheData {
+        int count = 0;
+        long long size = 0;
+        ushort refCount = 0;
+        qint64 timestamp = 0;
+
+        inline operator bool() const
+        {
+            return timestamp != 0 && count != -1;
+        }
+    };
 
-    if (!m_workerThread) {
-        m_workerThread = new QThread();
-        m_workerThread->start();
+    LocalCache()
+        : m_cache()
+    {
     }
 
-    if (s_cache == nullptr) {
-        s_cache = new QHash<QString, QPair<int, long>>();
+    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;
+    }
+
+    cacheData value(const QString &key) const
+    {
+        return m_cache.value(key);
     }
 
-    m_worker = new KDirectoryContentsCounterWorker();
-    m_worker->moveToThread(m_workerThread);
+    void unRefAll(const QSet<QString> &keys)
+    {
+        for (const auto &key : keys) {
+            auto entry = m_cache[key];
+            entry.refCount -= 1;
+            if (entry.refCount == 0) {
+                m_cache.remove(key);
+            }
+        }
+    }
+
+    void removeAll(const QSet<QString> &keys)
+    {
+        for (const auto &key : keys) {
+            m_cache.remove(key);
+        }
+    }
 
-    connect(this,     &KDirectoryContentsCounter::requestDirectoryContentsCount,
-            m_worker, &KDirectoryContentsCounterWorker::countDirectoryContents);
-    connect(m_worker, &KDirectoryContentsCounterWorker::result,
-            this,     &KDirectoryContentsCounter::slotResult);
+private:
+    QHash<QString, cacheData> m_cache;
+};
 
-    m_dirWatcher = new KDirWatch(this);
-    connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty);
+/// cache of directory counting result
+static LocalCache *s_cache;
+static QThread *s_workerThread;
+static KDirectoryContentsCounterWorker *s_worker;
 }
 
-KDirectoryContentsCounter::~KDirectoryContentsCounter()
+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_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;
+    if (s_cache == nullptr) {
+        s_cache = new LocalCache();
+    }
 
-        // 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;
+    if (!s_workerThread) {
+        s_workerThread = new QThread();
+        s_workerThread->setObjectName(QStringLiteral("KDirectoryContentsCounterThread"));
+        s_workerThread->start();
     }
+
+    if (!s_worker) {
+        s_worker = new KDirectoryContentsCounterWorker();
+        s_worker->moveToThread(s_workerThread);
+    }
+
+    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::scanDirectory(const QString& path)
+KDirectoryContentsCounter::~KDirectoryContentsCounter()
 {
-    startWorker(path);
+    s_cache->unRefAll(m_watchedDirs);
 }
 
-void KDirectoryContentsCounter::slotResult(const QString& path, int count, long size)
+void KDirectoryContentsCounter::slotResult(const QString &path, int count, long long size)
 {
-    m_workerIsBusy = false;
-
-    const QFileInfo info = QFileInfo(path);
-    const QString resolvedPath = info.canonicalFilePath();
-
-    if (!m_dirWatcher->contains(resolvedPath)) {
+    const auto fileInfo = QFileInfo(path);
+    const QString resolvedPath = fileInfo.canonicalFilePath();
+    if (fileInfo.isReadable() && !m_watchedDirs.contains(resolvedPath)) {
         m_dirWatcher->addDir(resolvedPath);
-        m_watchedDirs.insert(resolvedPath);
-    }
-
-    if (!m_queue.isEmpty()) {
-        startWorker(m_queue.takeFirst());
-    }
-
-    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;
-        }
     }
+    bool inserted = m_watchedDirs.insert(resolvedPath) == m_watchedDirs.end();
 
-    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<int, long>(count, size));
-    }
+    // update cache or overwrite value
+    s_cache->insert(resolvedPath, {count, size, true}, inserted);
 
     // sends the results
-    emit result(resolvedPath, count, size);
+    Q_EMIT result(path, count, size);
 }
 
-void KDirectoryContentsCounter::slotDirWatchDirty(const QStringpath)
+void KDirectoryContentsCounter::slotDirWatchDirty(const QString &path)
 {
     const int index = m_model->index(QUrl::fromLocalFile(path));
     if (index >= 0) {
@@ -136,7 +150,7 @@ void KDirectoryContentsCounter::slotDirWatchDirty(const QString& path)
             return;
         }
 
-        startWorker(path);
+        scanDirectory(path, PathCountPriority::High);
     }
 }
 
@@ -144,18 +158,22 @@ 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) {
-            for (const QStringpath : qAsConst(m_watchedDirs)) {
+            for (const QString &path : qAsConst(m_watchedDirs)) {
                 m_dirWatcher->removeDir(path);
             }
             m_watchedDirs.clear();
-            m_queue.clear();
         } else {
             QMutableSetIterator<QString> it(m_watchedDirs);
             while (it.hasNext()) {
-                const QStringpath = it.next();
+                const QString &path = it.next();
                 if (m_model->index(QUrl::fromLocalFile(path)) < 0) {
                     m_dirWatcher->removeDir(path);
                     it.remove();
@@ -165,33 +183,114 @@ void KDirectoryContentsCounter::slotItemsRemoved()
     }
 }
 
-void KDirectoryContentsCounter::startWorker(const QString& path)
+void KDirectoryContentsCounter::slotDirectoryRefreshing()
+{
+    s_cache->removeAll(m_watchedDirs);
+}
+
+void KDirectoryContentsCounter::scheduleNext()
 {
-    if (s_cache->contains(path)) {
+    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 {
+        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
-        const auto pair = s_cache->value(path);
-        emit result(path, pair.first, pair.second);
+        Q_EMIT result(m_currentPath, pair.count, pair.size);
     }
 
-    if (m_workerIsBusy) {
-        if (!m_queue.contains(path)) {
-            m_queue.append(path);
-        }
-    } else {
-        KDirectoryContentsCounterWorker::Options options;
+    // 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 = nullptr;
+#include "moc_kdirectorycontentscounter.cpp"