File indexing completed on 2024-05-12 05:47:29

0001 /*
0002  * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
0003  * SPDX-FileCopyrightText: 2013 Frank Reininghaus <frank78ac@googlemail.com>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kdirectorycontentscounterworker.h"
0009 
0010 // Required includes for countDirectoryContents():
0011 #ifdef Q_OS_WIN
0012 #include <QDir>
0013 #else
0014 #include <QElapsedTimer>
0015 #include <fts.h>
0016 #include <sys/stat.h>
0017 #include <sys/types.h>
0018 #endif
0019 
0020 KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject *parent)
0021     : QObject(parent)
0022 {
0023     qRegisterMetaType<KDirectoryContentsCounterWorker::Options>();
0024 }
0025 
0026 #ifndef Q_OS_WIN
0027 void KDirectoryContentsCounterWorker::walkDir(const QString &dirPath, bool countHiddenFiles, uint allowedRecursiveLevel)
0028 {
0029     QByteArray text = dirPath.toLocal8Bit();
0030     char *rootPath = new char[text.size() + 1];
0031     ::strncpy(rootPath, text.constData(), text.size() + 1);
0032     char *path[2]{rootPath, nullptr};
0033 
0034     // follow symlink only for root dir
0035     auto tree = ::fts_open(path, FTS_COMFOLLOW | FTS_PHYSICAL | FTS_XDEV, nullptr);
0036     if (!tree) {
0037         delete[] rootPath;
0038         return;
0039     }
0040 
0041     FTSENT *node;
0042     long long totalSize = -1;
0043     int totalCount = -1;
0044     QElapsedTimer timer;
0045     timer.start();
0046 
0047     while ((node = fts_read(tree)) && !m_stopping) {
0048         auto info = node->fts_info;
0049 
0050         if (info == FTS_DC) {
0051             // ignore directories clausing cycles
0052             continue;
0053         }
0054         if (info == FTS_DNR) {
0055             // ignore directories that can’t be read
0056             continue;
0057         }
0058         if (info == FTS_ERR) {
0059             // ignore directories causing errors
0060             fts_set(tree, node, FTS_SKIP);
0061             continue;
0062         }
0063         if (info == FTS_DP) {
0064             // ignore end traversal of dir
0065             continue;
0066         }
0067 
0068         if (!countHiddenFiles && node->fts_name[0] == '.' && strncmp(".git", node->fts_name, 4) != 0) {
0069             // skip hidden files, except .git dirs
0070             if (info == FTS_D) {
0071                 fts_set(tree, node, FTS_SKIP);
0072             }
0073             continue;
0074         }
0075 
0076         if (info == FTS_F) {
0077             // only count files that are physical (aka skip /proc/kcore...)
0078             // naive size counting not taking into account effective disk space used (aka size/block_size * block_size)
0079             // skip directory size (usually a 4KB block)
0080             if (node->fts_statp->st_blocks > 0) {
0081                 totalSize += node->fts_statp->st_size;
0082             }
0083         }
0084 
0085         if (info == FTS_D) {
0086             if (node->fts_level == 0) {
0087                 // first read was sucessful, we can init counters
0088                 totalSize = 0;
0089                 totalCount = 0;
0090             }
0091 
0092             if (node->fts_level > (int)allowedRecursiveLevel) {
0093                 // skip too deep nodes
0094                 fts_set(tree, node, FTS_SKIP);
0095                 continue;
0096             }
0097         }
0098         // count first level elements
0099         if (node->fts_level == 1) {
0100             ++totalCount;
0101         }
0102 
0103         // delay intermediate results
0104         if (timer.hasExpired(200) || node->fts_level == 0) {
0105             Q_EMIT intermediateResult(dirPath, totalCount, totalSize);
0106             timer.restart();
0107         }
0108     }
0109 
0110     delete[] rootPath;
0111     fts_close(tree);
0112     if (errno != 0) {
0113         return;
0114     }
0115 
0116     if (!m_stopping) {
0117         Q_EMIT result(dirPath, totalCount, totalSize);
0118     }
0119 }
0120 #endif
0121 
0122 void KDirectoryContentsCounterWorker::stop()
0123 {
0124     m_stopping = true;
0125 }
0126 
0127 bool KDirectoryContentsCounterWorker::stopping() const
0128 {
0129     return m_stopping;
0130 }
0131 
0132 QString KDirectoryContentsCounterWorker::scannedPath() const
0133 {
0134     return m_scannedPath;
0135 }
0136 
0137 void KDirectoryContentsCounterWorker::countDirectoryContents(const QString &path, Options options, int maxRecursiveLevel)
0138 {
0139     const bool countHiddenFiles = options & CountHiddenFiles;
0140 
0141 #ifdef Q_OS_WIN
0142     QDir dir(path);
0143     QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System | QDir::AllEntries;
0144     if (countHiddenFiles) {
0145         filters |= QDir::Hidden;
0146     }
0147 
0148     Q_EMIT result(path, static_cast<int>(dir.entryList(filters).count()), 0);
0149 #else
0150 
0151     m_scannedPath = path;
0152     walkDir(path, countHiddenFiles, maxRecursiveLevel);
0153 
0154 #endif
0155 
0156     m_stopping = false;
0157     Q_EMIT finished();
0158 }
0159 
0160 #include "moc_kdirectorycontentscounterworker.cpp"