From 8c9683a627dbc863c406de30afd1595e38fa266b Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 24 May 2023 15:27:03 +0200 Subject: [PATCH] FileSearch: Introduce oveload for findInFiles() taking FileContainer Handle gracefully pausing the search after exceeding the 200000 hits limit and continuing on user request. Change-Id: I4e9ea92cfdb5fa54706c862a654edb6169fce84a Reviewed-by: Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot --- src/libs/utils/filesearch.cpp | 131 +++++++++++++++++++++++++++++++++- src/libs/utils/filesearch.h | 3 + 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/src/libs/utils/filesearch.cpp b/src/libs/utils/filesearch.cpp index 7dd8951b91c..5d20d6a700f 100644 --- a/src/libs/utils/filesearch.cpp +++ b/src/libs/utils/filesearch.cpp @@ -4,6 +4,7 @@ #include "filesearch.h" #include "algorithm.h" +#include "async.h" #include "filepath.h" #include "mapreduce.h" #include "qtcassert.h" @@ -13,6 +14,7 @@ #include #include +#include #include #include @@ -331,9 +333,134 @@ void cleanUpFileSearch(QFutureInterface &futureInterface, } // namespace -QFuture Utils::findInFiles(const QString &searchTerm, FileIterator *files, - FindFlags flags, +static void fileSearch(QPromise &promise, + const FileContainerIterator::Item &item, const QString &searchTerm, + FindFlags flags, const QMap &fileToContentsMap) +{ + if (promise.isCanceled()) + return; + qCDebug(searchLog) << "Searching in" << item.filePath; + promise.setProgressRange(0, 1); + promise.setProgressValue(0); + QString contents; + if (!getFileContent(item.filePath, item.encoding, &contents, fileToContentsMap)) { + qCDebug(searchLog) << "- failed to get content for" << item.filePath; + promise.future().cancel(); // failure + return; + } + + const QFuture future(promise.future()); + const SearchResultItems results = searchInContents(future, searchTerm, flags, item.filePath, + contents); + if (!promise.isCanceled()) { + promise.addResult(results); + promise.setProgressValue(1); + } + qCDebug(searchLog) << "- finished searching in" << item.filePath; +} + +static void findInFilesImpl(QPromise &promise, const QString &searchTerm, + const FileContainer &container, FindFlags flags, + const QMap &fileToContentsMap) +{ + QEventLoop loop; + // The states transition exactly in this order: + enum State { BelowLimit, AboveLimit, Resumed }; + State state = BelowLimit; + int reportedItemsCount = 0; + int searchedFilesCount = 0; + + const int progressMaximum = container.progressMaximum(); + promise.setProgressRange(0, progressMaximum); + promise.setProgressValueAndText(0, msgFound(searchTerm, 0, 0)); + const int threadsCount = qMax(1, QThread::idealThreadCount() / 2); + QSet *> watchers; + FutureSynchronizer futureSynchronizer; + + const auto cleanup = qScopeGuard([&] { + qDeleteAll(watchers); + const QString message = promise.isCanceled() + ? msgCanceled(searchTerm, reportedItemsCount, searchedFilesCount) + : msgFound(searchTerm, reportedItemsCount, searchedFilesCount); + promise.setProgressValueAndText(progressMaximum, message); + }); + + FileContainerIterator it = container.begin(); + const FileContainerIterator itEnd = container.end(); + if (it == itEnd) + return; + + std::function scheduleNext; + scheduleNext = [&] { + if (promise.isCanceled() || (it == itEnd && watchers.isEmpty())) { + loop.quit(); + return; + } + if (it == itEnd) + return; + + if (state == AboveLimit) + return; + + if (watchers.size() == threadsCount) + return; + + const FileContainerIterator::Item item = *it; + const int progress = it.progressValue(); + ++it; + const QFuture future + = Utils::asyncRun(fileSearch, item, searchTerm, flags, fileToContentsMap); + QFutureWatcher *watcher = new QFutureWatcher; + QObject::connect(watcher, &QFutureWatcherBase::finished, &loop, + [watcher, progress, &searchTerm, &reportedItemsCount, &searchedFilesCount, &state, + &watchers, &promise, &scheduleNext] { + const QFuture future = watcher->future(); + if (future.resultCount()) { + const SearchResultItems items = future.result(); + reportedItemsCount += items.size(); + if (state == BelowLimit && reportedItemsCount > 200000) + state = AboveLimit; + if (!items.isEmpty()) + promise.addResult(items); + } + ++searchedFilesCount; + promise.setProgressValueAndText(progress, msgFound(searchTerm, reportedItemsCount, + searchedFilesCount)); + watcher->deleteLater(); + watchers.remove(watcher); + scheduleNext(); + }); + watcher->setFuture(future); + futureSynchronizer.addFuture(future); + watchers.insert(watcher); + scheduleNext(); + }; + + QFutureWatcher watcher; + QObject::connect(&watcher, &QFutureWatcherBase::canceled, &loop, &QEventLoop::quit); + QObject::connect(&watcher, &QFutureWatcherBase::resumed, &loop, [&state, &scheduleNext] { + state = Resumed; + scheduleNext(); + }); + + watcher.setFuture(QFuture(promise.future())); + + if (promise.isCanceled()) + return; + + QTimer::singleShot(0, &loop, scheduleNext); + loop.exec(QEventLoop::ExcludeUserInputEvents); +} + +QFuture Utils::findInFiles(const QString &searchTerm, + const FileContainer &container, FindFlags flags, const QMap &fileToContentsMap) +{ + return Utils::asyncRun(findInFilesImpl, searchTerm, container, flags, fileToContentsMap); +} + +QFuture Utils::findInFiles(const QString &searchTerm, FileIterator *files, + FindFlags flags, const QMap &fileToContentsMap) { return mapReduce(files->begin(), files->end(), [searchTerm, files](QFutureInterface &futureInterface) { diff --git a/src/libs/utils/filesearch.h b/src/libs/utils/filesearch.h index 81c5e2b8cd1..35724a8ec44 100644 --- a/src/libs/utils/filesearch.h +++ b/src/libs/utils/filesearch.h @@ -267,6 +267,9 @@ private: QList m_items; }; +QTCREATOR_UTILS_EXPORT QFuture findInFiles(const QString &searchTerm, + const FileContainer &container, FindFlags flags, const QMap &fileToContentsMap); + QTCREATOR_UTILS_EXPORT QFuture findInFiles(const QString &searchTerm, FileIterator *files, FindFlags flags, const QMap &fileToContentsMap);