-/***************************************************************************
- * 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 "kdirectorycontentscounterworker.h"
-// Required includes for subItemsCount():
-#ifdef Q_OS_WIN
- #include <QDir>
+// Required includes for countDirectoryContents():
+#if defined(Q_OS_WIN) || defined(Q_OS_HAIKU)
+#include <QDir>
#else
- #include <dirent.h>
- #include <QFile>
+#include <QElapsedTimer>
+#include <fts.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#endif
-KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject* parent) :
- QObject(parent)
+KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject *parent)
+ : QObject(parent)
{
qRegisterMetaType<KDirectoryContentsCounterWorker::Options>();
}
-int KDirectoryContentsCounterWorker::subItemsCount(const QString& path, Options options)
+#if !defined(Q_OS_WIN) && !defined(Q_OS_HAIKU)
+void KDirectoryContentsCounterWorker::walkDir(const QString &dirPath, bool countHiddenFiles, uint allowedRecursiveLevel)
{
- const bool countHiddenFiles = options & CountHiddenFiles;
- const bool countDirectoriesOnly = options & CountDirectoriesOnly;
+ QByteArray text = dirPath.toLocal8Bit();
+ char *rootPath = new char[text.size() + 1];
+ ::strncpy(rootPath, text.constData(), text.size() + 1);
+ char *path[2]{rootPath, nullptr};
-#ifdef Q_OS_WIN
- QDir dir(path);
- QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System;
- if (countHiddenFiles) {
- filters |= QDir::Hidden;
+ // follow symlink only for root dir
+ auto tree = ::fts_open(path, FTS_COMFOLLOW | FTS_PHYSICAL | FTS_XDEV, nullptr);
+ if (!tree) {
+ delete[] rootPath;
+ return;
}
- if (countDirectoriesOnly) {
- filters |= QDir::Dirs;
- } else {
- filters |= QDir::AllEntries;
- }
- return dir.entryList(filters).count();
-#else
- // Taken from kdelibs/kio/kio/kdirmodel.cpp
- // Copyright (C) 2006 David Faure <faure@kde.org>
-
- int count = -1;
- DIR* dir = ::opendir(QFile::encodeName(path));
- if (dir) { // krazy:exclude=syscalls
- count = 0;
- struct dirent *dirEntry = nullptr;
- while ((dirEntry = ::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;
- }
+
+ 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 (info == FTS_D) {
+ if (node->fts_level == 0) {
+ // first read was sucessful, we can init counters
+ totalSize = 0;
+ totalCount = 0;
}
- // 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 (node->fts_level > (int)allowedRecursiveLevel) {
+ // skip too deep nodes
+ fts_set(tree, node, FTS_SKIP);
+ continue;
}
}
- ::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;
}
- return count;
+
+ if (!m_stopping) {
+ Q_EMIT result(dirPath, totalCount, totalSize);
+ }
+}
#endif
+
+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)
+void KDirectoryContentsCounterWorker::countDirectoryContents(const QString &path, Options options, int maxRecursiveLevel)
{
- emit result(path, subItemsCount(path, options));
+ const bool countHiddenFiles = options & CountHiddenFiles;
+
+#if defined(Q_OS_WIN) || defined(Q_OS_HAIKU)
+ QDir dir(path);
+ QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System | QDir::AllEntries;
+ if (countHiddenFiles) {
+ filters |= QDir::Hidden;
+ }
+
+ Q_EMIT result(path, static_cast<int>(dir.entryList(filters).count()), 0);
+#else
+
+ m_scannedPath = path;
+ walkDir(path, countHiddenFiles, maxRecursiveLevel);
+
+#endif
+
+ m_stopping = false;
+ Q_EMIT finished();
}
+
+#include "moc_kdirectorycontentscounterworker.cpp"