File indexing completed on 2024-04-28 05:49:11
0001 /* 0002 SPDX-FileCopyrightText: 2013 Kåre Särs <kare.sars@iki.fi> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "FolderFilesList.h" 0008 0009 #include <QDebug> 0010 #include <QDir> 0011 #include <QElapsedTimer> 0012 #include <QFileInfoList> 0013 #include <QtConcurrent> 0014 0015 #include <unordered_set> 0016 #include <vector> 0017 0018 FolderFilesList::FolderFilesList(QObject *parent) 0019 : QThread(parent) 0020 { 0021 // ensure we have a proper thread name during e.g. perf profiling 0022 setObjectName(QStringLiteral("FolderFilesList")); 0023 } 0024 0025 FolderFilesList::~FolderFilesList() 0026 { 0027 m_cancelSearch = true; 0028 wait(); 0029 } 0030 0031 void FolderFilesList::run() 0032 { 0033 m_files.clear(); 0034 0035 /** 0036 * iterative algorithm, in each round, we put in X directories to traverse 0037 * we will get as output X times: new directories + found files 0038 */ 0039 std::vector<DirectoryWithResults> directoriesWithResults{DirectoryWithResults{m_folder, QStringList(), QStringList()}}; 0040 std::unordered_set<QString> directoryGuard{m_folder}; 0041 QElapsedTimer time; 0042 time.start(); 0043 while (!directoriesWithResults.empty()) { 0044 /** 0045 * all 100 ms => inform about progress 0046 */ 0047 if (time.elapsed() > 100) { 0048 time.restart(); 0049 Q_EMIT searching(directoriesWithResults[0].directory); 0050 } 0051 0052 /** 0053 * map the stuff blocking, we are in a background thread, easiest way 0054 * this will just span ideal thread count many things while this thread more or less sleeps 0055 * we call the checkNextItem member function, that one must be careful to not do evil things ;=) 0056 */ 0057 QtConcurrent::blockingMap(directoriesWithResults, [this](DirectoryWithResults &item) { 0058 checkNextItem(item); 0059 }); 0060 0061 /** 0062 * collect the results to create new worklist for next round 0063 */ 0064 std::vector<DirectoryWithResults> nextRound; 0065 for (const auto &result : directoriesWithResults) { 0066 /** 0067 * one new item for the next round for each new directory 0068 */ 0069 for (const auto &newDirectory : result.newDirectories) { 0070 if (directoryGuard.insert(newDirectory).second) { 0071 nextRound.push_back(DirectoryWithResults{newDirectory, QStringList(), QStringList()}); 0072 } 0073 } 0074 0075 /** 0076 * just append found files 0077 */ 0078 m_files << result.newFiles; 0079 } 0080 0081 /** 0082 * let's get next round going 0083 */ 0084 directoriesWithResults = nextRound; 0085 } 0086 0087 if (m_cancelSearch) { 0088 m_files.clear(); 0089 } 0090 Q_EMIT fileListReady(); 0091 } 0092 0093 void FolderFilesList::generateList(const QString &folder, bool recursive, bool hidden, bool symlinks, const QString &types, const QString &excludes) 0094 { 0095 m_cancelSearch = false; 0096 m_folder = folder; 0097 if (!m_folder.endsWith(QLatin1Char('/'))) { 0098 m_folder += QLatin1Char('/'); 0099 } 0100 m_recursive = recursive; 0101 m_hidden = hidden; 0102 m_symlinks = symlinks; 0103 0104 m_types.clear(); 0105 const auto typesList = types.split(QLatin1Char(','), Qt::SkipEmptyParts); 0106 for (const QString &type : typesList) { 0107 m_types << type.trimmed(); 0108 } 0109 if (m_types.isEmpty()) { 0110 m_types << QStringLiteral("*"); 0111 } 0112 0113 QStringList tmpExcludes = excludes.split(QLatin1Char(',')); 0114 m_excludes.clear(); 0115 for (int i = 0; i < tmpExcludes.size(); i++) { 0116 m_excludes << QRegularExpression(QRegularExpression::wildcardToRegularExpression(tmpExcludes[i].trimmed())); 0117 } 0118 0119 start(); 0120 } 0121 0122 void FolderFilesList::terminateSearch() 0123 { 0124 m_cancelSearch = true; 0125 wait(); 0126 m_files.clear(); 0127 } 0128 0129 QStringList FolderFilesList::fileList() 0130 { 0131 if (m_cancelSearch) { 0132 m_files.clear(); 0133 } 0134 return m_files; 0135 } 0136 0137 void FolderFilesList::checkNextItem(DirectoryWithResults &handleOnFolder) const 0138 { 0139 /** 0140 * IMPORTANT: this member function is called by MULTIPLE THREADS 0141 * => it is const, it shall only modify handleOnFolder 0142 */ 0143 0144 if (m_cancelSearch) { 0145 return; 0146 } 0147 0148 QDir currentDir(handleOnFolder.directory); 0149 if (!currentDir.isReadable()) { 0150 // qDebug() << currentDir.absolutePath() << "Not readable"; 0151 return; 0152 } 0153 0154 QDir::Filters filter = QDir::Files | QDir::NoDotAndDotDot | QDir::Readable; 0155 if (m_hidden) { 0156 filter |= QDir::Hidden; 0157 } 0158 if (m_recursive) { 0159 filter |= QDir::AllDirs; 0160 } 0161 if (!m_symlinks) { 0162 filter |= QDir::NoSymLinks; 0163 } 0164 0165 const QFileInfoList entries = currentDir.entryInfoList(m_types, filter, QDir::Name | QDir::LocaleAware); 0166 for (const auto &entry : entries) { 0167 const QString absFilePath = entry.absoluteFilePath(); 0168 bool skip{false}; 0169 const QStringList pathSplit = absFilePath.split(QLatin1Char('/'), Qt::SkipEmptyParts); 0170 for (const auto ®ex : m_excludes) { 0171 for (const auto &part : pathSplit) { 0172 QRegularExpressionMatch match = regex.match(part); 0173 if (match.hasMatch()) { 0174 skip = true; 0175 break; 0176 } 0177 } 0178 } 0179 if (skip) { 0180 continue; 0181 } 0182 0183 if (entry.isDir()) { 0184 handleOnFolder.newDirectories.append(absFilePath); 0185 } 0186 0187 if (entry.isFile()) { 0188 handleOnFolder.newFiles.append(absFilePath); 0189 } 0190 } 0191 } 0192 0193 #include "moc_FolderFilesList.cpp"