]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/private/kdirectorycontentscounterworker.cpp
KDirectoryContentsCounter: show intermediate dir size counting results, improve stopp...
[dolphin.git] / src / kitemviews / private / kdirectorycontentscounterworker.cpp
index 2227ebbc262953fb7ad7d1373ebde61282232c99..eb456da254b1376cd6558a63e14c00fa431dba43 100644 (file)
@@ -7,16 +7,16 @@
 
 #include "kdirectorycontentscounterworker.h"
 
-// Required includes for subItemsCount():
+// Required includes for countDirectoryContents():
 #ifdef Q_OS_WIN
 #include <QDir>
 #else
-#include <QFile>
-#include <qplatformdefs.h>
+#include <QElapsedTimer>
+#include <fts.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #endif
 
-#include "dolphin_detailsmodesettings.h"
-
 KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject *parent)
     : QObject(parent)
 {
@@ -24,100 +24,135 @@ KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject *parent
 }
 
 #ifndef Q_OS_WIN
-KDirectoryContentsCounterWorker::CountResult
-KDirectoryContentsCounterWorker::walkDir(const QString &dirPath, const bool countHiddenFiles, const bool countDirectoriesOnly, const uint allowedRecursiveLevel)
+void KDirectoryContentsCounterWorker::walkDir(const QString &dirPath, bool countHiddenFiles, uint allowedRecursiveLevel)
 {
-    int count = -1;
-    long size = -1;
-    auto dir = QT_OPENDIR(QFile::encodeName(dirPath));
-    if (dir) {
-        count = 0;
-        size = 0;
-        QT_DIRENT *dirEntry;
-        QT_STATBUF buf;
-
-        while (!m_stopping && (dirEntry = QT_READDIR(dir))) {
-            if (dirEntry->d_name[0] == '.') {
-                if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) {
-                    // Skip "." or hidden files
-                    continue;
-                }
-                if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
-                    // Skip ".."
-                    continue;
-                }
+    QByteArray text = dirPath.toLocal8Bit();
+    char *rootPath = new char[text.size() + 1];
+    ::strncpy(rootPath, text.constData(), text.size() + 1);
+    char *path[2]{rootPath, nullptr};
+
+    // follow symlink only for root dir
+    auto tree = ::fts_open(path, FTS_COMFOLLOW | FTS_PHYSICAL | FTS_XDEV, nullptr);
+    if (!tree) {
+        delete[] rootPath;
+        return;
+    }
+
+    FTSENT *node;
+    long long totalSize = -1;
+    int totalCount = -1;
+    QElapsedTimer timer;
+    timer.start();
+
+    while ((node = fts_read(tree)) && !m_stopping) {
+        auto info = node->fts_info;
+
+        if (info == FTS_DC) {
+            // ignore directories clausing cycles
+            continue;
+        }
+        if (info == FTS_DNR) {
+            // ignore directories that can’t be read
+            continue;
+        }
+        if (info == FTS_ERR) {
+            // ignore directories causing errors
+            fts_set(tree, node, FTS_SKIP);
+            continue;
+        }
+        if (info == FTS_DP) {
+            // ignore end traversal of dir
+            continue;
+        }
+
+        if (!countHiddenFiles && node->fts_name[0] == '.' && strncmp(".git", node->fts_name, 4) != 0) {
+            // skip hidden files, except .git dirs
+            if (info == FTS_D) {
+                fts_set(tree, node, FTS_SKIP);
+            }
+            continue;
+        }
+
+        if (info == FTS_F) {
+            // only count files that are physical (aka skip /proc/kcore...)
+            // naive size counting not taking into account effective disk space used (aka size/block_size * block_size)
+            // skip directory size (usually a 4KB block)
+            if (node->fts_statp->st_blocks > 0) {
+                totalSize += node->fts_statp->st_size;
             }
+        }
 
-            // If only directories are counted, consider an unknown file type and links also
-            // as directory instead of trying to do an expensive stat()
-            // (see bugs 292642 and 299997).
-            const bool countEntry = !countDirectoriesOnly || dirEntry->d_type == DT_DIR || dirEntry->d_type == DT_LNK || dirEntry->d_type == DT_UNKNOWN;
-            if (countEntry) {
-                ++count;
+        if (info == FTS_D) {
+            if (node->fts_level == 0) {
+                // first read was sucessful, we can init counters
+                totalSize = 0;
+                totalCount = 0;
             }
 
-            if (allowedRecursiveLevel > 0) {
-                QString nameBuf = QStringLiteral("%1/%2").arg(dirPath, dirEntry->d_name);
-
-                if (dirEntry->d_type == DT_REG) {
-                    if (QT_STAT(nameBuf.toLocal8Bit(), &buf) == 0) {
-                        size += buf.st_size;
-                    }
-                }
-                if (!m_stopping && dirEntry->d_type == DT_DIR) {
-                    // recursion for dirs
-                    auto subdirResult = walkDir(nameBuf, countHiddenFiles, countDirectoriesOnly, allowedRecursiveLevel - 1);
-                    if (subdirResult.size > 0) {
-                        size += subdirResult.size;
-                    }
-                }
+            if (node->fts_level > (int)allowedRecursiveLevel) {
+                // skip too deep nodes
+                fts_set(tree, node, FTS_SKIP);
+                continue;
             }
         }
-        QT_CLOSEDIR(dir);
+        // count first level elements
+        if (node->fts_level == 1) {
+            ++totalCount;
+        }
+
+        // delay intermediate results
+        if (timer.hasExpired(200) || node->fts_level == 0) {
+            Q_EMIT intermediateResult(dirPath, totalCount, totalSize);
+            timer.restart();
+        }
+    }
+
+    delete[] rootPath;
+    fts_close(tree);
+    if (errno != 0) {
+        return;
+    }
+
+    if (!m_stopping) {
+        Q_EMIT result(dirPath, totalCount, totalSize);
     }
-    return KDirectoryContentsCounterWorker::CountResult{count, size};
 }
 #endif
 
-KDirectoryContentsCounterWorker::CountResult KDirectoryContentsCounterWorker::subItemsCount(const QString &path, Options options)
+void KDirectoryContentsCounterWorker::stop()
+{
+    m_stopping = true;
+}
+
+bool KDirectoryContentsCounterWorker::stopping() const
+{
+    return m_stopping;
+}
+
+QString KDirectoryContentsCounterWorker::scannedPath() const
+{
+    return m_scannedPath;
+}
+
+void KDirectoryContentsCounterWorker::countDirectoryContents(const QString &path, Options options, int maxRecursiveLevel)
 {
     const bool countHiddenFiles = options & CountHiddenFiles;
-    const bool countDirectoriesOnly = options & CountDirectoriesOnly;
 
 #ifdef Q_OS_WIN
     QDir dir(path);
-    QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System;
+    QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System | QDir::AllEntries;
     if (countHiddenFiles) {
         filters |= QDir::Hidden;
     }
-    if (countDirectoriesOnly) {
-        filters |= QDir::Dirs;
-    } else {
-        filters |= QDir::AllEntries;
-    }
-    return {static_cast<int>(dir.entryList(filters).count()), 0};
-#else
 
-    const uint maxRecursiveLevel = DetailsModeSettings::directorySizeCount() ? 1 : DetailsModeSettings::recursiveDirectorySizeLimit();
+    Q_EMIT result(path, static_cast<int>(dir.entryList(filters).count()), 0);
+#else
 
-    auto res = walkDir(path, countHiddenFiles, countDirectoriesOnly, maxRecursiveLevel);
+    m_scannedPath = path;
+    walkDir(path, countHiddenFiles, maxRecursiveLevel);
 
-    return res;
 #endif
-}
-
-void KDirectoryContentsCounterWorker::stop()
-{
-    m_stopping = true;
-}
 
-void KDirectoryContentsCounterWorker::countDirectoryContents(const QString &path, Options options)
-{
     m_stopping = false;
-
-    auto res = subItemsCount(path, options);
-
-    if (!m_stopping) {
-        Q_EMIT result(path, res.count, res.size);
-    }
+    Q_EMIT finished();
 }