FolderFilesList.cpp 5.55 KB
Newer Older
1
2
3
4
5
/*
    SPDX-FileCopyrightText: 2013 Kåre Särs <kare.sars@iki.fi>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/
Kåre Särs's avatar
Kåre Särs committed
6

7
#include "FolderFilesList.h"
Christoph Cullmann's avatar
Christoph Cullmann committed
8

9
#include <QDebug>
Kåre Särs's avatar
Kåre Särs committed
10
#include <QDir>
11
#include <QElapsedTimer>
12
#include <QFileInfoList>
13
14
15
16
#include <QtConcurrent>

#include <unordered_set>
#include <vector>
Kåre Särs's avatar
Kåre Särs committed
17

18
19
20
FolderFilesList::FolderFilesList(QObject *parent)
    : QThread(parent)
{
Christoph Cullmann's avatar
Christoph Cullmann committed
21
22
    // ensure we have a proper thread name during e.g. perf profiling
    setObjectName(QStringLiteral("FolderFilesList"));
23
}
24
25

FolderFilesList::~FolderFilesList()
Kåre Särs's avatar
Kåre Särs committed
26
{
27
28
29
30
31
32
33
34
    m_cancelSearch = true;
    wait();
}

void FolderFilesList::run()
{
    m_files.clear();

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
    /**
     * iterative algorithm, in each round, we put in X directories to traverse
     * we will get as output X times: new directories + found files
     */
    std::vector<DirectoryWithResults> directoriesWithResults{DirectoryWithResults{m_folder, QStringList(), QStringList()}};
    std::unordered_set<QString> directoryGuard{m_folder};
    QElapsedTimer time;
    time.start();
    while (!directoriesWithResults.empty()) {
        /**
         * all 100 ms => inform about progress
         */
        if (time.elapsed() > 100) {
            time.restart();
            Q_EMIT searching(directoriesWithResults[0].directory);
        }

        /**
         * map the stuff blocking, we are in a background thread, easiest way
         * this will just span ideal thread count many things while this thread more or less sleeps
         * we call the checkNextItem member function, that one must be careful to not do evil things ;=)
         */
        QtConcurrent::blockingMap(directoriesWithResults, [this](DirectoryWithResults &item) {
            checkNextItem(item);
        });

        /**
         * collect the results to create new worklist for next round
         */
        std::vector<DirectoryWithResults> nextRound;
        for (const auto &result : directoriesWithResults) {
            /**
             * one new item for the next round for each new directory
             */
            for (const auto &newDirectory : result.newDirectories) {
                if (directoryGuard.insert(newDirectory).second) {
                    nextRound.push_back(DirectoryWithResults{newDirectory, QStringList(), QStringList()});
                }
            }

            /**
             * just append found files
             */
            m_files << result.newFiles;
        }

        /**
         * let's get next round going
         */
        directoriesWithResults = nextRound;
    }
86

87
    if (m_cancelSearch) {
88
        m_files.clear();
89
    }
90
    Q_EMIT fileListReady();
Kåre Särs's avatar
Kåre Särs committed
91
92
}

93
void FolderFilesList::generateList(const QString &folder, bool recursive, bool hidden, bool symlinks, const QString &types, const QString &excludes)
Kåre Särs's avatar
Kåre Särs committed
94
95
{
    m_cancelSearch = false;
96
    m_folder = folder;
97
98
99
    if (!m_folder.endsWith(QLatin1Char('/'))) {
        m_folder += QLatin1Char('/');
    }
100
101
102
    m_recursive = recursive;
    m_hidden = hidden;
    m_symlinks = symlinks;
103

104
    m_types.clear();
Laurent Montel's avatar
Laurent Montel committed
105
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
106
    const auto typesList = types.split(QLatin1Char(','), QString::SkipEmptyParts);
Laurent Montel's avatar
Laurent Montel committed
107
108
109
#else
    const auto typesList = types.split(QLatin1Char(','), Qt::SkipEmptyParts);
#endif
110
    for (const QString &type : typesList) {
111
112
        m_types << type.trimmed();
    }
113
    if (m_types.isEmpty()) {
Joseph Wenninger's avatar
Joseph Wenninger committed
114
        m_types << QStringLiteral("*");
115
    }
116

117
    QStringList tmpExcludes = excludes.split(QLatin1Char(','));
118
    m_excludes.clear();
119
    for (int i = 0; i < tmpExcludes.size(); i++) {
120
        m_excludes << QRegularExpression(QRegularExpression::wildcardToRegularExpression(tmpExcludes[i].trimmed()));
121
    }
Kåre Särs's avatar
Kåre Särs committed
122
123
124
125

    start();
}

126
127
128
129
void FolderFilesList::terminateSearch()
{
    m_cancelSearch = true;
    wait();
130
    m_files.clear();
131
132
}

133
134
QStringList FolderFilesList::fileList()
{
135
136
137
    if (m_cancelSearch) {
        m_files.clear();
    }
138
139
    return m_files;
}
Kåre Särs's avatar
Kåre Särs committed
140

141
void FolderFilesList::checkNextItem(DirectoryWithResults &handleOnFolder) const
Kåre Särs's avatar
Kåre Särs committed
142
{
143
144
145
146
147
    /**
     * IMPORTANT: this member function is called by MULTIPLE THREADS
     * => it is const, it shall only modify handleOnFolder
     */

Kåre Särs's avatar
Kåre Särs committed
148
149
150
    if (m_cancelSearch) {
        return;
    }
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166

    QDir currentDir(handleOnFolder.directory);
    if (!currentDir.isReadable()) {
        // qDebug() << currentDir.absolutePath() << "Not readable";
        return;
    }

    QDir::Filters filter = QDir::Files | QDir::NoDotAndDotDot | QDir::Readable;
    if (m_hidden) {
        filter |= QDir::Hidden;
    }
    if (m_recursive) {
        filter |= QDir::AllDirs;
    }
    if (!m_symlinks) {
        filter |= QDir::NoSymLinks;
167
    }
Kåre Särs's avatar
Kåre Särs committed
168

169
170
171
172
    const QFileInfoList entries = currentDir.entryInfoList(m_types, filter, QDir::Name | QDir::LocaleAware);
    for (const auto &entry : entries) {
        const QString absFilePath = entry.absoluteFilePath();
        bool skip{false};
Kåre Särs's avatar
Kåre Särs committed
173
174
175
176
177
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
        const QStringList pathSplit = absFilePath.split(QLatin1Char('/'), QString::SkipEmptyParts);
#else
        const QStringList pathSplit = absFilePath.split(QLatin1Char('/'), Qt::SkipEmptyParts);
#endif
178
        for (const auto &regex : m_excludes) {
Kåre Särs's avatar
Kåre Särs committed
179
180
181
182
183
184
            for (const auto &part : pathSplit) {
                QRegularExpressionMatch match = regex.match(part);
                if (match.hasMatch()) {
                    skip = true;
                    break;
                }
185
            }
186
        }
187
188
        if (skip) {
            continue;
189
        }
Kåre Särs's avatar
Kåre Särs committed
190

191
192
193
        if (entry.isDir()) {
            handleOnFolder.newDirectories.append(absFilePath);
        }
194

195
196
        if (entry.isFile()) {
            handleOnFolder.newFiles.append(absFilePath);
Kåre Särs's avatar
Kåre Särs committed
197
198
199
        }
    }
}