┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/kitemviews/private/kdirectorycontentscounterworker.cpp
blob: 8a69fb4e23dde6b926015a18d5fff81f3ecdca3b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/*
 * SPDX-FileCopyrightText: 2011 Peter Penz <[email protected]>
 * SPDX-FileCopyrightText: 2013 Frank Reininghaus <[email protected]>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "kdirectorycontentscounterworker.h"

// Required includes for countDirectoryContents():
#if defined(Q_OS_WIN) || defined(Q_OS_HAIKU)
#include <QDir>
#else
#include <QElapsedTimer>
#include <fts.h>
#include <sys/stat.h>
#include <sys/types.h>
#endif

KDirectoryContentsCounterWorker::KDirectoryContentsCounterWorker(QObject *parent)
    : QObject(parent)
{
    qRegisterMetaType<KDirectoryContentsCounterWorker::Options>();
}

#if !defined(Q_OS_WIN) && !defined(Q_OS_HAIKU)
void KDirectoryContentsCounterWorker::walkDir(const QString &dirPath, bool countHiddenFiles, uint allowedRecursiveLevel)
{
    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 (info == FTS_D) {
            if (node->fts_level == 0) {
                // first read was successful, we can init counters
                totalSize = 0;
                totalCount = 0;
            }

            if (node->fts_level > (int)allowedRecursiveLevel) {
                // skip too deep nodes
                fts_set(tree, node, FTS_SKIP);
                continue;
            }
        }
        // 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);
    }
}
#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, int maxRecursiveLevel)
{
    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"