Redesign file browser searching (#7130)
This commit is contained in:
@@ -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 '*'
|
||||
|
||||
@@ -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
73
include/FileSearch.h
Normal 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
100
include/ThreadPool.h
Normal 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
|
||||
Reference in New Issue
Block a user