Analyze and improve search in the file browser (again) (#6985)
Improves performance when searching in the file browser (confirmed with profiling using KCacheGrind), adds a search indicator at the bottom to let the user know a search is in progress, blacklists unnecessary system directories (speeding up both the search speed and potentially load times as well by reducing the number of filesystem entries to consider), and fixes an issue that causes not all of the search results to appear.
This commit is contained in:
@@ -14,6 +14,7 @@ SET(LMMS_SRCS
|
||||
gui/EffectView.cpp
|
||||
gui/embed.cpp
|
||||
gui/FileBrowser.cpp
|
||||
gui/FileBrowserSearcher.cpp
|
||||
gui/GuiApplication.cpp
|
||||
gui/LadspaControlView.cpp
|
||||
gui/LfoControllerDialog.cpp
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
#include <QShortcut>
|
||||
#include <QStringList>
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "FileBrowser.h"
|
||||
#include "FileBrowserSearcher.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "DataFile.h"
|
||||
@@ -130,7 +133,6 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter,
|
||||
m_filterEdit->addAction(embed::getIconPixmap("zoom"), QLineEdit::LeadingPosition);
|
||||
|
||||
connect(m_filterEdit, &QLineEdit::textEdited, this, &FileBrowser::onSearch);
|
||||
connect(FileBrowserSearcher::instance(), &FileBrowserSearcher::searchComplete, this, &FileBrowser::buildSearchTree);
|
||||
|
||||
auto reload_btn = new QPushButton(embed::getIconPixmap("reload"), QString(), searchWidget);
|
||||
reload_btn->setToolTip( tr( "Refresh list" ) );
|
||||
@@ -149,6 +151,15 @@ 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);
|
||||
addContentWidget(m_searchIndicator);
|
||||
|
||||
// Whenever the FileBrowser has focus, Ctrl+F should direct focus to its filter box.
|
||||
auto filterFocusShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this, SLOT(giveFocusToFilter()));
|
||||
filterFocusShortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
||||
@@ -159,11 +170,6 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter,
|
||||
show();
|
||||
}
|
||||
|
||||
QDir::Filters FileBrowser::dirFilters()
|
||||
{
|
||||
return QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot;
|
||||
}
|
||||
|
||||
void FileBrowser::saveDirectoriesStates()
|
||||
{
|
||||
m_savedExpandedDirs = m_fileBrowserTreeWidget->expandedDirs();
|
||||
@@ -174,72 +180,104 @@ void FileBrowser::restoreDirectoriesStates()
|
||||
expandItems(nullptr, m_savedExpandedDirs);
|
||||
}
|
||||
|
||||
void FileBrowser::buildSearchTree(QStringList matches, QString id)
|
||||
void FileBrowser::buildSearchTree()
|
||||
{
|
||||
if (title() != id) { return; }
|
||||
if (!m_currentSearch) { return; }
|
||||
|
||||
m_searchTreeWidget->clear();
|
||||
|
||||
const auto rootPaths = m_directories.split('*');
|
||||
for (const auto& rootPath : rootPaths)
|
||||
const auto match = m_currentSearch->match();
|
||||
using State = FileBrowserSearcher::SearchFuture::State;
|
||||
if ((m_currentSearch->state() == State::Completed && match.isEmpty())
|
||||
|| m_currentSearch->state() == State::Cancelled)
|
||||
{
|
||||
const auto rootPathDir = QDir{rootPath};
|
||||
const auto absoluteRootPath = rootPathDir.absolutePath();
|
||||
m_currentSearch = nullptr;
|
||||
m_searchIndicator->setMaximum(100);
|
||||
return;
|
||||
}
|
||||
else if (match.isEmpty()) { return; }
|
||||
|
||||
for (const auto& match : matches)
|
||||
{
|
||||
if (!match.startsWith(absoluteRootPath)) { continue; }
|
||||
|
||||
const auto childInfo = QFileInfo{match};
|
||||
const auto childName = childInfo.fileName();
|
||||
const auto parentPath = childInfo.dir().path();
|
||||
auto childWidget = static_cast<QTreeWidgetItem*>(nullptr);
|
||||
|
||||
if (childInfo.isDir())
|
||||
{
|
||||
auto dirChildWidget = new Directory(childName, parentPath, m_filter);
|
||||
dirChildWidget->update();
|
||||
childWidget = dirChildWidget;
|
||||
}
|
||||
else if (childInfo.isFile()) { childWidget = new FileItem(childName, parentPath); }
|
||||
else { continue; }
|
||||
|
||||
const auto relativeParentPath = rootPathDir.relativeFilePath(parentPath);
|
||||
if (relativeParentPath == ".")
|
||||
{
|
||||
m_searchTreeWidget->addTopLevelItem(childWidget);
|
||||
if (childInfo.isDir()) { m_searchTreeWidget->expandItem(childWidget); }
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto grandParentPath = QFileInfo{parentPath}.dir().path();
|
||||
const auto parentItems = m_searchTreeWidget->findItems(relativeParentPath, Qt::MatchExactly);
|
||||
|
||||
if (parentItems.isEmpty())
|
||||
{
|
||||
auto parentItem = new Directory(relativeParentPath, grandParentPath, m_filter);
|
||||
parentItem->addChild(childWidget);
|
||||
m_searchTreeWidget->addTopLevelItem(parentItem);
|
||||
m_searchTreeWidget->expandItem(parentItem);
|
||||
}
|
||||
else { parentItems[0]->addChild(childWidget); }
|
||||
}
|
||||
auto basePath = QString{};
|
||||
for (const auto& path : m_directories.split('*'))
|
||||
{
|
||||
if (!match.startsWith(QDir{path}.absolutePath())) { continue; }
|
||||
basePath = path;
|
||||
break;
|
||||
}
|
||||
|
||||
toggleSearch(true);
|
||||
if (basePath.isEmpty()) { return; }
|
||||
|
||||
const auto baseDir = QDir{basePath};
|
||||
const auto matchInfo = QFileInfo{match};
|
||||
const auto matchRelativeToBasePath = baseDir.relativeFilePath(match);
|
||||
|
||||
auto pathParts = QDir::cleanPath(matchRelativeToBasePath).split("/");
|
||||
auto currentItem = static_cast<QTreeWidgetItem*>(nullptr);
|
||||
auto currentDir = baseDir;
|
||||
|
||||
for (const auto& pathPart : pathParts)
|
||||
{
|
||||
auto childCount = currentItem ? currentItem->childCount() : m_searchTreeWidget->topLevelItemCount();
|
||||
auto childItem = static_cast<QTreeWidgetItem*>(nullptr);
|
||||
|
||||
for (int i = 0; i < childCount; ++i)
|
||||
{
|
||||
auto item = currentItem ? currentItem->child(i) : m_searchTreeWidget->topLevelItem(i);
|
||||
if (item->text(0) == pathPart)
|
||||
{
|
||||
childItem = item;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!childItem)
|
||||
{
|
||||
auto pathPartInfo = QFileInfo(currentDir, pathPart);
|
||||
if (pathPartInfo.isDir())
|
||||
{
|
||||
// Only update directory (i.e., add entries) when it is the matched directory (so do not update
|
||||
// parents since entries would be added to them that did not match the filter)
|
||||
const auto disablePopulation = pathParts.indexOf(pathPart) < pathParts.size() - 1;
|
||||
|
||||
auto item = new Directory(pathPart, currentDir.path(), m_filter, disablePopulation);
|
||||
currentItem ? currentItem->addChild(item) : m_searchTreeWidget->addTopLevelItem(item);
|
||||
item->update();
|
||||
if (disablePopulation) { m_searchTreeWidget->expandItem(item); }
|
||||
childItem = item;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto item = new FileItem(pathPart, currentDir.path());
|
||||
currentItem ? currentItem->addChild(item) : m_searchTreeWidget->addTopLevelItem(item);
|
||||
childItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
currentItem = childItem;
|
||||
if (!currentDir.cd(pathPart)) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FileBrowser::onSearch(const QString& filter)
|
||||
{
|
||||
auto instance = FileBrowserSearcher::instance();
|
||||
if (filter.isEmpty())
|
||||
{
|
||||
toggleSearch(false);
|
||||
instance->cancel();
|
||||
FileBrowserSearcher::instance()->cancel();
|
||||
return;
|
||||
}
|
||||
instance->search({m_directories, filter, dirFilters(), m_filter.split(' '), title()});
|
||||
|
||||
auto directories = m_directories.split('*');
|
||||
if (m_showUserContent && !m_showUserContent->isChecked()) { directories.removeAll(m_userDir); }
|
||||
if (m_showFactoryContent && !m_showFactoryContent->isChecked()) { directories.removeAll(m_factoryDir); }
|
||||
if (directories.isEmpty()) { return; }
|
||||
|
||||
m_searchTreeWidget->clear();
|
||||
toggleSearch(true);
|
||||
|
||||
auto browserExtensions = m_filter;
|
||||
const auto searchExtensions = browserExtensions.remove("*.").split(' ');
|
||||
m_currentSearch = FileBrowserSearcher::instance()->search(filter, directories, searchExtensions);
|
||||
}
|
||||
|
||||
void FileBrowser::toggleSearch(bool on)
|
||||
@@ -248,90 +286,13 @@ void FileBrowser::toggleSearch(bool on)
|
||||
{
|
||||
m_searchTreeWidget->show();
|
||||
m_fileBrowserTreeWidget->hide();
|
||||
m_searchIndicator->setMaximum(0);
|
||||
return;
|
||||
}
|
||||
|
||||
m_searchTreeWidget->hide();
|
||||
m_fileBrowserTreeWidget->show();
|
||||
}
|
||||
|
||||
bool FileBrowser::filterAndExpandItems(const QString & filter, QTreeWidgetItem * item)
|
||||
{
|
||||
// Call with item = nullptr to filter the entire tree
|
||||
|
||||
if (item == nullptr)
|
||||
{
|
||||
// First search character so need to save current expanded directories
|
||||
if (m_previousFilterValue.isEmpty())
|
||||
{
|
||||
saveDirectoriesStates();
|
||||
}
|
||||
|
||||
m_previousFilterValue = filter;
|
||||
}
|
||||
|
||||
if (filter.isEmpty())
|
||||
{
|
||||
// Restore previous expanded directories
|
||||
if (item == nullptr)
|
||||
{
|
||||
restoreDirectoriesStates();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool anyMatched = false;
|
||||
|
||||
int numChildren = item ? item->childCount() : m_fileBrowserTreeWidget->topLevelItemCount();
|
||||
|
||||
for (int i = 0; i < numChildren; ++i)
|
||||
{
|
||||
QTreeWidgetItem * it = item ? item->child( i ) : m_fileBrowserTreeWidget->topLevelItem(i);
|
||||
|
||||
auto d = dynamic_cast<Directory*>(it);
|
||||
if (d)
|
||||
{
|
||||
if (it->text(0).contains(filter, Qt::CaseInsensitive))
|
||||
{
|
||||
it->setHidden(false);
|
||||
it->setExpanded(true);
|
||||
filterAndExpandItems(QString(), it);
|
||||
anyMatched = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Expanding is required when recursive to load in its contents, even if it's collapsed right afterward
|
||||
it->setExpanded(true);
|
||||
|
||||
bool didMatch = filterAndExpandItems(filter, it);
|
||||
it->setHidden(!didMatch);
|
||||
it->setExpanded(didMatch);
|
||||
anyMatched = anyMatched || didMatch;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
auto f = dynamic_cast<FileItem*>(it);
|
||||
if (f)
|
||||
{
|
||||
// File
|
||||
bool didMatch = it->text(0).contains(filter, Qt::CaseInsensitive);
|
||||
it->setHidden(!didMatch);
|
||||
anyMatched = anyMatched || didMatch;
|
||||
}
|
||||
|
||||
// A standard item (i.e. no file or directory item?)
|
||||
else
|
||||
{
|
||||
// Hide if there's any filter
|
||||
it->setHidden(!filter.isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return anyMatched;
|
||||
m_searchIndicator->setMaximum(100);
|
||||
}
|
||||
|
||||
|
||||
@@ -370,7 +331,7 @@ void FileBrowser::reloadTree()
|
||||
}
|
||||
else
|
||||
{
|
||||
filterAndExpandItems(m_filterEdit->text());
|
||||
onSearch(m_filterEdit->text());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,6 +377,8 @@ void FileBrowser::giveFocusToFilter()
|
||||
|
||||
void FileBrowser::addItems(const QString & path )
|
||||
{
|
||||
if (FileBrowser::directoryBlacklist().contains(path)) { return; }
|
||||
|
||||
if( m_dirsAsItems )
|
||||
{
|
||||
m_fileBrowserTreeWidget->addTopLevelItem( new Directory( path, QString(), m_filter ) );
|
||||
@@ -429,6 +392,8 @@ 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; }
|
||||
|
||||
QString fileName = entry.fileName();
|
||||
if (entry.isDir())
|
||||
{
|
||||
@@ -1047,107 +1012,16 @@ void FileBrowserTreeWidget::updateDirectory(QTreeWidgetItem * item )
|
||||
}
|
||||
|
||||
|
||||
|
||||
FileBrowserSearcher::FileBrowserSearcher()
|
||||
: m_worker([this] { run(); })
|
||||
{
|
||||
}
|
||||
|
||||
FileBrowserSearcher::~FileBrowserSearcher() noexcept
|
||||
{
|
||||
m_cancel = true;
|
||||
{
|
||||
const auto runLock = std::lock_guard{m_runMutex};
|
||||
m_stopped = true;
|
||||
m_cancel = false;
|
||||
}
|
||||
m_runCond.notify_one();
|
||||
m_worker.join();
|
||||
}
|
||||
|
||||
void FileBrowserSearcher::search(SearchTask task)
|
||||
{
|
||||
m_cancel = true;
|
||||
{
|
||||
const auto runLock = std::lock_guard{m_runMutex};
|
||||
m_currentTask = std::move(task);
|
||||
m_run = true;
|
||||
m_cancel = false;
|
||||
}
|
||||
m_runCond.notify_one();
|
||||
}
|
||||
|
||||
void FileBrowserSearcher::cancel()
|
||||
{
|
||||
m_cancel = true;
|
||||
}
|
||||
|
||||
void FileBrowserSearcher::run()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
auto lock = std::unique_lock{m_runMutex};
|
||||
m_runCond.wait(lock, [this] { return m_run || m_stopped; });
|
||||
|
||||
if (m_stopped) { break; }
|
||||
|
||||
filter();
|
||||
m_run = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowserSearcher::filter()
|
||||
{
|
||||
const auto& [directories, userFilter, filters, nameFilters, id] = m_currentTask;
|
||||
const auto paths = directories.split('*');
|
||||
auto matches = QStringList{};
|
||||
|
||||
for (const auto& path : paths)
|
||||
{
|
||||
auto it = QDirIterator{path, nameFilters, filters, QDirIterator::Subdirectories};
|
||||
while (it.hasNext())
|
||||
{
|
||||
it.next();
|
||||
const auto name = it.fileName();
|
||||
const auto path = it.filePath();
|
||||
if (!inHiddenDirectory(path) && name.contains(userFilter, Qt::CaseInsensitive)) { matches.push_back(path); }
|
||||
if (m_cancel) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
emit searchComplete(matches, id);
|
||||
}
|
||||
|
||||
FileBrowserSearcher* FileBrowserSearcher::instance()
|
||||
{
|
||||
if (!s_instance) { s_instance = std::make_unique<FileBrowserSearcher>(); }
|
||||
return s_instance.get();
|
||||
}
|
||||
|
||||
bool FileBrowserSearcher::inHiddenDirectory(const QString& path)
|
||||
{
|
||||
auto dir = QDir{path};
|
||||
while (!dir.isRoot())
|
||||
{
|
||||
auto info = QFileInfo{dir.path()};
|
||||
if (info.isHidden()) { return true; }
|
||||
dir.cdUp();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
QPixmap * Directory::s_folderPixmap = nullptr;
|
||||
QPixmap * Directory::s_folderOpenedPixmap = nullptr;
|
||||
QPixmap * Directory::s_folderLockedPixmap = nullptr;
|
||||
|
||||
|
||||
Directory::Directory(const QString & filename, const QString & path,
|
||||
const QString & filter ) :
|
||||
QTreeWidgetItem( QStringList( filename ), TypeDirectoryItem ),
|
||||
m_directories( path ),
|
||||
m_filter( filter ),
|
||||
m_dirCount( 0 )
|
||||
Directory::Directory(const QString& filename, const QString& path, const QString& filter, bool disableEntryPopulation)
|
||||
: QTreeWidgetItem(QStringList(filename), TypeDirectoryItem)
|
||||
, m_directories(path)
|
||||
, m_filter(filter)
|
||||
, m_dirCount(0)
|
||||
, m_disableEntryPopulation(disableEntryPopulation)
|
||||
{
|
||||
initPixmaps();
|
||||
|
||||
@@ -1199,7 +1073,7 @@ void Directory::update()
|
||||
}
|
||||
|
||||
setIcon( 0, *s_folderOpenedPixmap );
|
||||
if( !childCount() )
|
||||
if (!m_disableEntryPopulation && !childCount())
|
||||
{
|
||||
m_dirCount = 0;
|
||||
// for all paths leading here, add their items
|
||||
@@ -1232,17 +1106,19 @@ void Directory::update()
|
||||
|
||||
bool Directory::addItems(const QString& path)
|
||||
{
|
||||
if (FileBrowser::directoryBlacklist().contains(path)) { return false; }
|
||||
|
||||
QDir thisDir(path);
|
||||
if (!thisDir.isReadable()) { return false; }
|
||||
|
||||
treeWidget()->setUpdatesEnabled(false);
|
||||
|
||||
QFileInfoList entries = thisDir.entryInfoList(
|
||||
m_filter.split(' '),
|
||||
QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot,
|
||||
QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase);
|
||||
QFileInfoList entries
|
||||
= thisDir.entryInfoList(m_filter.split(' '), FileBrowser::dirFilters(), FileBrowser::sortFlags());
|
||||
for (const auto& entry : entries)
|
||||
{
|
||||
if (FileBrowser::directoryBlacklist().contains(entry.absoluteFilePath())) { continue; }
|
||||
|
||||
QString fileName = entry.fileName();
|
||||
if (entry.isDir())
|
||||
{
|
||||
|
||||
135
src/gui/FileBrowserSearcher.cpp
Normal file
135
src/gui/FileBrowserSearcher.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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
|
||||
Reference in New Issue
Block a user