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 &regex : 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"