Redesign file browser searching (#7130)

This commit is contained in:
saker
2024-05-29 21:46:58 -04:00
committed by GitHub
parent e82e3f573a
commit bfeb781dc0
10 changed files with 403 additions and 326 deletions

View File

@@ -28,10 +28,11 @@
#include <QCheckBox>
#include <QDir>
#include <QMutex>
#include "embed.h"
#include "FileBrowserSearcher.h"
#include <QProgressBar>
#include <memory>
#include "FileSearch.h"
#include "embed.h"
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
#include <QRecursiveMutex>
@@ -76,9 +77,9 @@ public:
~FileBrowser() override = default;
static QStringList directoryBlacklist()
static QStringList excludedPaths()
{
static auto s_blacklist = QStringList{
static auto s_excludedPaths = QStringList{
#ifdef LMMS_BUILD_LINUX
"/bin", "/boot", "/dev", "/etc", "/proc", "/run", "/sbin",
"/sys"
@@ -87,7 +88,7 @@ public:
"C:\\Windows"
#endif
};
return s_blacklist;
return s_excludedPaths;
}
static QDir::Filters dirFilters() { return QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot; }
static QDir::SortFlags sortFlags() { return QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase; }
@@ -105,16 +106,17 @@ private:
void saveDirectoriesStates();
void restoreDirectoriesStates();
void buildSearchTree();
void foundSearchMatch(FileSearch* search, const QString& match);
void searchCompleted(FileSearch* search);
void onSearch(const QString& filter);
void toggleSearch(bool on);
void displaySearch(bool on);
FileBrowserTreeWidget * m_fileBrowserTreeWidget;
FileBrowserTreeWidget * m_searchTreeWidget;
QLineEdit * m_filterEdit;
std::shared_ptr<FileBrowserSearcher::SearchFuture> m_currentSearch;
std::shared_ptr<FileSearch> m_currentSearch;
QProgressBar* m_searchIndicator = nullptr;
QString m_directories; //!< Directories to search, split with '*'

View File

@@ -1,148 +0,0 @@
/*
* FileBrowserSearcher.h - Batch processor for searching the filesystem
*
* Copyright (c) 2023 saker <sakertooth@gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef LMMS_FILE_BROWSER_SEARCHER_H
#define LMMS_FILE_BROWSER_SEARCHER_H
#include <QHash>
#include <QString>
#include <QStringList>
#include <optional>
#include <queue>
#ifdef __MINGW32__
#include <mingw.condition_variable.h>
#include <mingw.mutex.h>
#include <mingw.thread.h>
#else
#include <condition_variable>
#include <mutex>
#include <thread>
#endif
namespace lmms::gui {
//! An active object that handles searching for files that match a certain filter across the file system.
class FileBrowserSearcher
{
public:
//! Number of milliseconds to wait for before a match should be processed by the user.
static constexpr int MillisecondsPerMatch = 1;
//! The future object for FileBrowserSearcher. It is used to track the current state of search operations, as
// well as retrieve matches.
class SearchFuture
{
public:
//! Possible state values of the future object.
enum class State
{
Idle,
Running,
Cancelled,
Completed
};
//! Constructs a future object using the specified filter, paths, and valid file extensions in the Idle state.
SearchFuture(const QString& filter, const QStringList& paths, const QStringList& extensions)
: m_filter(filter)
, m_paths(paths)
, m_extensions(extensions)
{
}
//! Retrieves a match from the match list.
auto match() -> QString
{
const auto lock = std::lock_guard{m_matchesMutex};
return m_matches.empty() ? QString{} : m_matches.takeFirst();
}
//! Returns the current state of this future object.
auto state() -> State { return m_state; }
//! Returns the filter used.
auto filter() -> const QString& { return m_filter; }
//! Returns the paths to filter.
auto paths() -> const QStringList& { return m_paths; }
//! Returns the valid file extensions.
auto extensions() -> const QStringList& { return m_extensions; }
private:
//! Adds a match to the match list.
auto addMatch(const QString& match) -> void
{
const auto lock = std::lock_guard{m_matchesMutex};
m_matches.append(match);
}
QString m_filter;
QStringList m_paths;
QStringList m_extensions;
QStringList m_matches;
std::mutex m_matchesMutex;
std::atomic<State> m_state = State::Idle;
friend FileBrowserSearcher;
};
~FileBrowserSearcher();
//! Enqueues a search to be ran by the worker thread.
//! Returns a future that the caller can use to track state and results of the operation.
auto search(const QString& filter, const QStringList& paths, const QStringList& extensions)
-> std::shared_ptr<SearchFuture>;
//! Sends a signal to cancel a running search.
auto cancel() -> void { m_cancelRunningSearch = true; }
//! Returns the global instance of the searcher object.
static auto instance() -> FileBrowserSearcher*
{
static auto s_instance = FileBrowserSearcher{};
return &s_instance;
}
private:
//! Event loop for the worker thread.
auto run() -> void;
//! Using Depth-first search (DFS), filters the specified path and adds any matches to the future list.
auto process(SearchFuture* searchFuture, const QString& path) -> bool;
std::queue<std::shared_ptr<SearchFuture>> m_searchQueue;
std::atomic<bool> m_cancelRunningSearch = false;
bool m_workerStopped = false;
std::mutex m_workerMutex;
std::condition_variable m_workerCond;
std::thread m_worker{[this] { run(); }};
};
} // namespace lmms::gui
#endif // LMMS_FILE_BROWSER_SEARCHER_H

73
include/FileSearch.h Normal file
View File

@@ -0,0 +1,73 @@
/*
* FileSearch.h - File system search task
*
* Copyright (c) 2024 saker
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef LMMS_FILE_SEARCH_H
#define LMMS_FILE_SEARCH_H
#include <QDir>
#include <QObject>
#include <atomic>
namespace lmms {
//! A Qt object that encapsulates the operation of searching the file system.
class FileSearch : public QObject
{
Q_OBJECT
public:
//! Number of milliseconds the search waits before signaling a matching result.
static constexpr int MillisecondsBetweenResults = 1;
//! Create a `FileSearch` object that uses the specified string filter `filter` and extension filters in
//! `extensions` to search within the given `paths`.
//! `excludedPaths`, `dirFilters`, and `sortFlags` can optionally be specified to exclude certain directories, filter
//! out certain types of entries, and sort the matches.
FileSearch(const QString& filter, const QStringList& paths, const QStringList& extensions,
const QStringList& excludedPaths = {}, QDir::Filters dirFilters = QDir::Filters{},
QDir::SortFlags sortFlags = QDir::SortFlags{});
//! Execute the search, emitting the `foundResult` signal when matches are found.
void operator()();
//! Cancel the search.
void cancel();
signals:
//! Emitted when a result is found when searching the file system.
void foundMatch(FileSearch* search, const QString& match);
//! Emitted when the search completes.
void searchCompleted(FileSearch* search);
private:
static auto isPathExcluded(const QString& path) -> bool;
QString m_filter;
QStringList m_paths;
QStringList m_extensions;
QStringList m_excludedPaths;
QDir::Filters m_dirFilters;
QDir::SortFlags m_sortFlags;
std::atomic<bool> m_cancel = false;
};
} // namespace lmms
#endif // LMMS_FILE_SEARCH_H

100
include/ThreadPool.h Normal file
View File

@@ -0,0 +1,100 @@
/*
* ThreadPool.h
*
* Copyright (c) 2024 saker
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef LMMS_THREAD_POOL_H
#define LMMS_THREAD_POOL_H
#include <atomic>
#include <queue>
#include <tuple>
#include <type_traits>
#include <vector>
#ifdef __MINGW32__
#include <mingw.condition_variable.h>
#include <mingw.future.h>
#include <mingw.mutex.h>
#include <mingw.thread.h>
#else
#include <condition_variable>
#include <future>
#include <mutex>
#include <thread>
#endif
namespace lmms {
//! A thread pool that can be used for asynchronous processing.
class ThreadPool
{
public:
//! Destroys the `ThreadPool` object.
//! This blocks until all workers have finished executing.
~ThreadPool();
//! Enqueue function `fn` with arguments `args` to be ran asynchronously.
template <typename Fn, typename... Args>
auto enqueue(Fn&& fn, Args&&... args) -> std::future<std::invoke_result_t<Fn, Args...>>
{
using ReturnType = std::invoke_result_t<Fn, Args...>;
auto promise = std::make_shared<std::promise<ReturnType>>();
auto task = [promise, fn = std::forward<Fn>(fn), args = std::make_tuple(std::forward<Args>(args)...)]
{
if constexpr (!std::is_same_v<ReturnType, void>)
{
promise->set_value(std::apply(fn, args));
return;
}
std::apply(fn, args);
promise->set_value();
};
{
const auto lock = std::unique_lock{m_runMutex};
m_queue.push(std::move(task));
}
m_runCond.notify_one();
return promise->get_future();
}
//! Return the number of worker threads used.
auto numWorkers() const -> size_t;
//! Return the global `ThreadPool` instance.
static auto instance() -> ThreadPool&;
private:
ThreadPool(size_t numWorkers);
void run();
std::vector<std::thread> m_workers;
std::queue<std::function<void()>> m_queue;
std::atomic<bool> m_done = false;
std::condition_variable m_runCond;
std::mutex m_runMutex;
inline static size_t s_numWorkers = std::thread::hardware_concurrency();
};
} // namespace lmms
#endif // LMMS_THREAD_POOL_H

View File

@@ -23,6 +23,7 @@ set(LMMS_SRCS
core/Engine.cpp
core/EnvelopeAndLfoParameters.cpp
core/fft_helpers.cpp
core/FileSearch.cpp
core/Mixer.cpp
core/ImportFilter.cpp
core/InlineAutomation.cpp
@@ -76,6 +77,7 @@ set(LMMS_SRCS
core/SerializingObject.cpp
core/Song.cpp
core/TempoSyncKnobModel.cpp
core/ThreadPool.cpp
core/Timeline.cpp
core/TimePos.cpp
core/ToolPlugin.cpp

96
src/core/FileSearch.cpp Normal file
View File

@@ -0,0 +1,96 @@
/*
* FileSearch.cpp - File system search task
*
* Copyright (c) 2024 saker
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "FileSearch.h"
#include <atomic>
#include <chrono>
#include <lmmsconfig.h>
#ifdef __MINGW32__
#include <mingw.thread.h>
#else
#include <thread>
#endif
namespace lmms {
FileSearch::FileSearch(const QString& filter, const QStringList& paths, const QStringList& extensions,
const QStringList& excludedPaths, QDir::Filters dirFilters, QDir::SortFlags sortFlags)
: m_filter(filter)
, m_paths(paths)
, m_extensions(extensions)
, m_excludedPaths(excludedPaths)
, m_dirFilters(dirFilters)
, m_sortFlags(sortFlags)
{
}
void FileSearch::operator()()
{
auto stack = QFileInfoList{};
for (const auto& path : m_paths)
{
if (m_excludedPaths.contains(path)) { continue; }
auto dir = QDir{path};
stack.append(dir.entryInfoList(m_dirFilters, m_sortFlags));
while (!stack.empty())
{
if (m_cancel.load(std::memory_order_relaxed)) { return; }
const auto info = stack.takeFirst();
const auto entryPath = info.absoluteFilePath();
if (m_excludedPaths.contains(entryPath)) { continue; }
const auto name = info.fileName();
const auto validFile = info.isFile() && m_extensions.contains(info.suffix(), Qt::CaseInsensitive);
const auto passesFilter = name.contains(m_filter, Qt::CaseInsensitive);
if ((validFile || info.isDir()) && passesFilter)
{
std::this_thread::sleep_for(std::chrono::milliseconds{MillisecondsBetweenResults});
emit foundMatch(this, entryPath);
}
if (info.isDir())
{
dir.setPath(entryPath);
const auto entries = dir.entryInfoList(m_dirFilters, m_sortFlags);
// Reverse to maintain the sorting within this directory when popped
std::for_each(entries.rbegin(), entries.rend(), [&stack](const auto& entry) { stack.push_front(entry); });
}
}
}
emit searchCompleted(this);
}
void FileSearch::cancel()
{
m_cancel.store(true, std::memory_order_relaxed);
}
} // namespace lmms

85
src/core/ThreadPool.cpp Normal file
View File

@@ -0,0 +1,85 @@
/*
* ThreadPool.cpp
*
* Copyright (c) 2024 saker
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "ThreadPool.h"
#include <cassert>
#include <cstddef>
#include <memory>
namespace lmms {
ThreadPool::ThreadPool(size_t numWorkers)
{
assert(numWorkers > 0);
m_workers.reserve(numWorkers);
for (size_t i = 0; i < numWorkers; ++i)
{
m_workers.emplace_back([this] { run(); });
}
}
ThreadPool::~ThreadPool()
{
{
const auto lock = std::unique_lock{m_runMutex};
m_done = true;
}
m_runCond.notify_all();
for (auto& worker : m_workers)
{
if (worker.joinable()) { worker.join(); }
}
}
auto ThreadPool::numWorkers() const -> size_t
{
return m_workers.size();
}
void ThreadPool::run()
{
while (!m_done)
{
std::function<void()> task;
{
auto lock = std::unique_lock{m_runMutex};
m_runCond.wait(lock, [this] { return !m_queue.empty() || m_done; });
if (m_done) { break; }
task = m_queue.front();
m_queue.pop();
}
task();
}
}
auto ThreadPool::instance() -> ThreadPool&
{
static auto s_pool = ThreadPool{s_numWorkers};
return s_pool;
}
} // namespace lmms

View File

@@ -14,7 +14,6 @@ SET(LMMS_SRCS
gui/EffectView.cpp
gui/embed.cpp
gui/FileBrowser.cpp
gui/FileBrowserSearcher.cpp
gui/GuiApplication.cpp
gui/LadspaControlView.cpp
gui/LfoControllerDialog.cpp

View File

@@ -31,23 +31,22 @@
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLineEdit>
#include <QMenu>
#include <QPushButton>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QMenu>
#include <QMessageBox>
#include <QPushButton>
#include <QShortcut>
#include <QStringList>
#include <cassert>
#include <queue>
#include "FileBrowser.h"
#include "FileBrowserSearcher.h"
#include "AudioEngine.h"
#include "ConfigManager.h"
#include "DataFile.h"
#include "embed.h"
#include "Engine.h"
#include "FileBrowser.h"
#include "FileSearch.h"
#include "GuiApplication.h"
#include "ImportFilter.h"
#include "Instrument.h"
@@ -65,6 +64,8 @@
#include "Song.h"
#include "StringPairDrag.h"
#include "TextFloat.h"
#include "ThreadPool.h"
#include "embed.h"
namespace lmms::gui
{
@@ -152,10 +153,6 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter,
m_searchTreeWidget->hide();
addContentWidget(m_searchTreeWidget);
auto searchTimer = new QTimer(this);
connect(searchTimer, &QTimer::timeout, this, &FileBrowser::buildSearchTree);
searchTimer->start(FileBrowserSearcher::MillisecondsPerMatch);
m_searchIndicator = new QProgressBar(this);
m_searchIndicator->setMinimum(0);
m_searchIndicator->setMaximum(100);
@@ -181,20 +178,10 @@ void FileBrowser::restoreDirectoriesStates()
expandItems(m_savedExpandedDirs);
}
void FileBrowser::buildSearchTree()
void FileBrowser::foundSearchMatch(FileSearch* search, const QString& match)
{
if (!m_currentSearch) { return; }
const auto match = m_currentSearch->match();
using State = FileBrowserSearcher::SearchFuture::State;
if ((m_currentSearch->state() == State::Completed && match.isEmpty())
|| m_currentSearch->state() == State::Cancelled)
{
m_currentSearch = nullptr;
m_searchIndicator->setMaximum(100);
return;
}
else if (match.isEmpty()) { return; }
assert(search != nullptr);
if (m_currentSearch.get() != search) { return; }
auto basePath = QString{};
for (const auto& path : m_directories.split('*'))
@@ -258,13 +245,22 @@ void FileBrowser::buildSearchTree()
}
}
void FileBrowser::searchCompleted(FileSearch* search)
{
assert(search != nullptr);
if (m_currentSearch.get() != search) { return; }
m_currentSearch.reset();
m_searchIndicator->setMaximum(100);
}
void FileBrowser::onSearch(const QString& filter)
{
if (m_currentSearch) { m_currentSearch->cancel(); }
if (filter.isEmpty())
{
toggleSearch(false);
FileBrowserSearcher::instance()->cancel();
displaySearch(false);
return;
}
@@ -274,14 +270,21 @@ void FileBrowser::onSearch(const QString& filter)
if (directories.isEmpty()) { return; }
m_searchTreeWidget->clear();
toggleSearch(true);
displaySearch(true);
auto browserExtensions = m_filter;
const auto searchExtensions = browserExtensions.remove("*.").split(' ');
m_currentSearch = FileBrowserSearcher::instance()->search(filter, directories, searchExtensions);
auto search = std::make_shared<FileSearch>(
filter, directories, searchExtensions, excludedPaths(), dirFilters(), sortFlags());
connect(search.get(), &FileSearch::foundMatch, this, &FileBrowser::foundSearchMatch, Qt::QueuedConnection);
connect(search.get(), &FileSearch::searchCompleted, this, &FileBrowser::searchCompleted, Qt::QueuedConnection);
m_currentSearch = search;
ThreadPool::instance().enqueue([search] { (*search)(); });
}
void FileBrowser::toggleSearch(bool on)
void FileBrowser::displaySearch(bool on)
{
if (on)
{
@@ -376,7 +379,7 @@ void FileBrowser::giveFocusToFilter()
void FileBrowser::addItems(const QString & path )
{
if (FileBrowser::directoryBlacklist().contains(path)) { return; }
if (FileBrowser::excludedPaths().contains(path)) { return; }
if( m_dirsAsItems )
{
@@ -391,7 +394,7 @@ void FileBrowser::addItems(const QString & path )
m_filter.split(' '), dirFilters(), QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase);
for (const auto& entry : entries)
{
if (FileBrowser::directoryBlacklist().contains(entry.absoluteFilePath())) { continue; }
if (FileBrowser::excludedPaths().contains(entry.absoluteFilePath())) { continue; }
QString fileName = entry.fileName();
if (entry.isDir())
@@ -1066,7 +1069,7 @@ void Directory::update()
bool Directory::addItems(const QString& path)
{
if (FileBrowser::directoryBlacklist().contains(path)) { return false; }
if (FileBrowser::excludedPaths().contains(path)) { return false; }
QDir thisDir(path);
if (!thisDir.isReadable()) { return false; }
@@ -1077,7 +1080,7 @@ bool Directory::addItems(const QString& path)
= thisDir.entryInfoList(m_filter.split(' '), FileBrowser::dirFilters(), FileBrowser::sortFlags());
for (const auto& entry : entries)
{
if (FileBrowser::directoryBlacklist().contains(entry.absoluteFilePath())) { continue; }
if (FileBrowser::excludedPaths().contains(entry.absoluteFilePath())) { continue; }
QString fileName = entry.fileName();
if (entry.isDir())

View File

@@ -1,135 +0,0 @@
/*
* FileBrowserSearcher.cpp - Batch processor for searching the filesystem
*
* Copyright (c) 2023 saker <sakertooth@gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "FileBrowserSearcher.h"
#include <QDir>
#include <stack>
#include "FileBrowser.h"
namespace lmms::gui {
FileBrowserSearcher::~FileBrowserSearcher()
{
m_cancelRunningSearch = true;
{
const auto lock = std::lock_guard{m_workerMutex};
m_workerStopped = true;
}
m_workerCond.notify_one();
m_worker.join();
}
auto FileBrowserSearcher::search(const QString& filter, const QStringList& paths, const QStringList& extensions)
-> std::shared_ptr<SearchFuture>
{
m_cancelRunningSearch = true;
auto future = std::make_shared<SearchFuture>(filter, paths, extensions);
{
const auto lock = std::lock_guard{m_workerMutex};
m_searchQueue.push(future);
m_cancelRunningSearch = false;
}
m_workerCond.notify_one();
return future;
}
auto FileBrowserSearcher::run() -> void
{
while (true)
{
auto lock = std::unique_lock{m_workerMutex};
m_workerCond.wait(lock, [this] { return m_workerStopped || !m_searchQueue.empty(); });
if (m_workerStopped) { return; }
const auto future = m_searchQueue.front();
future->m_state = SearchFuture::State::Running;
m_searchQueue.pop();
auto cancelled = false;
for (const auto& path : future->m_paths)
{
if (FileBrowser::directoryBlacklist().contains(path)) { continue; }
if (!process(future.get(), path))
{
future->m_state = SearchFuture::State::Cancelled;
cancelled = true;
break;
}
}
if (!cancelled) { future->m_state = SearchFuture::State::Completed; }
}
}
auto FileBrowserSearcher::process(SearchFuture* searchFuture, const QString& path) -> bool
{
auto stack = QFileInfoList{};
auto dir = QDir{path};
stack.append(dir.entryInfoList(FileBrowser::dirFilters(), FileBrowser::sortFlags()));
while (!stack.empty())
{
if (m_cancelRunningSearch)
{
m_cancelRunningSearch = false;
return false;
}
const auto info = stack.takeFirst();
const auto path = info.absoluteFilePath();
if (FileBrowser::directoryBlacklist().contains(path)) { continue; }
const auto name = info.fileName();
const auto validFile = info.isFile() && searchFuture->m_extensions.contains(info.suffix(), Qt::CaseInsensitive);
const auto passesFilter = name.contains(searchFuture->m_filter, Qt::CaseInsensitive);
// Only when a directory doesn't pass the filter should we search further
if (info.isDir() && !passesFilter)
{
dir.setPath(path);
auto entries = dir.entryInfoList(FileBrowser::dirFilters(), FileBrowser::sortFlags());
// Reverse to maintain the sorting within this directory when popped
std::reverse(entries.begin(), entries.end());
for (const auto& entry : entries)
{
stack.push_front(entry);
}
}
else if ((validFile || info.isDir()) && passesFilter) { searchFuture->addMatch(path); }
}
return true;
}
} // namespace lmms::gui