|
|
@ -2,6 +2,7 @@ |
|
|
// Licensed under GPLv2 or any later version
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
// Refer to the license.txt file included.
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
|
|
|
|
#include <QApplication>
|
|
|
#include <QFileInfo>
|
|
|
#include <QFileInfo>
|
|
|
#include <QHeaderView>
|
|
|
#include <QHeaderView>
|
|
|
#include <QKeyEvent>
|
|
|
#include <QKeyEvent>
|
|
|
@ -194,6 +195,9 @@ void GameList::onFilterCloseClicked() { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
GameList::GameList(GMainWindow* parent) : QWidget{parent} { |
|
|
GameList::GameList(GMainWindow* parent) : QWidget{parent} { |
|
|
|
|
|
watcher = new QFileSystemWatcher(this); |
|
|
|
|
|
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); |
|
|
|
|
|
|
|
|
this->main_window = parent; |
|
|
this->main_window = parent; |
|
|
layout = new QVBoxLayout; |
|
|
layout = new QVBoxLayout; |
|
|
tree_view = new QTreeView; |
|
|
tree_view = new QTreeView; |
|
|
@ -218,7 +222,6 @@ GameList::GameList(GMainWindow* parent) : QWidget{parent} { |
|
|
|
|
|
|
|
|
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); |
|
|
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); |
|
|
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); |
|
|
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); |
|
|
connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); |
|
|
|
|
|
|
|
|
|
|
|
// We must register all custom types with the Qt Automoc system so that we are able to use it
|
|
|
// We must register all custom types with the Qt Automoc system so that we are able to use it
|
|
|
// with signals/slots. In this case, QList falls under the umbrells of custom types.
|
|
|
// with signals/slots. In this case, QList falls under the umbrells of custom types.
|
|
|
@ -269,7 +272,22 @@ void GameList::ValidateEntry(const QModelIndex& item) { |
|
|
emit GameChosen(file_path); |
|
|
emit GameChosen(file_path); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void GameList::DonePopulating() { |
|
|
|
|
|
|
|
|
void GameList::DonePopulating(QStringList watch_list) { |
|
|
|
|
|
// Clear out the old directories to watch for changes and add the new ones
|
|
|
|
|
|
auto watch_dirs = watcher->directories(); |
|
|
|
|
|
if (!watch_dirs.isEmpty()) { |
|
|
|
|
|
watcher->removePaths(watch_dirs); |
|
|
|
|
|
} |
|
|
|
|
|
// Workaround: Add the watch paths in chunks to allow the gui to refresh
|
|
|
|
|
|
// This prevents the UI from stalling when a large number of watch paths are added
|
|
|
|
|
|
// Also artificially caps the watcher to a certain number of directories
|
|
|
|
|
|
constexpr int LIMIT_WATCH_DIRECTORIES = 5000; |
|
|
|
|
|
constexpr int SLICE_SIZE = 25; |
|
|
|
|
|
int len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES); |
|
|
|
|
|
for (int i = 0; i < len; i += SLICE_SIZE) { |
|
|
|
|
|
watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE)); |
|
|
|
|
|
QCoreApplication::processEvents(); |
|
|
|
|
|
} |
|
|
tree_view->setEnabled(true); |
|
|
tree_view->setEnabled(true); |
|
|
int rowCount = tree_view->model()->rowCount(); |
|
|
int rowCount = tree_view->model()->rowCount(); |
|
|
search_field->setFilterResult(rowCount, rowCount); |
|
|
search_field->setFilterResult(rowCount, rowCount); |
|
|
@ -309,11 +327,6 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { |
|
|
|
|
|
|
|
|
emit ShouldCancelWorker(); |
|
|
emit ShouldCancelWorker(); |
|
|
|
|
|
|
|
|
auto watch_dirs = watcher.directories(); |
|
|
|
|
|
if (!watch_dirs.isEmpty()) { |
|
|
|
|
|
watcher.removePaths(watch_dirs); |
|
|
|
|
|
} |
|
|
|
|
|
UpdateWatcherList(dir_path.toStdString(), deep_scan ? 256 : 0); |
|
|
|
|
|
GameListWorker* worker = new GameListWorker(dir_path, deep_scan); |
|
|
GameListWorker* worker = new GameListWorker(dir_path, deep_scan); |
|
|
|
|
|
|
|
|
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); |
|
|
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); |
|
|
@ -359,38 +372,6 @@ void GameList::RefreshGameDirectory() { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Adds the game list folder to the QFileSystemWatcher to check for updates. |
|
|
|
|
|
* |
|
|
|
|
|
* The file watcher will fire off an update to the game list when a change is detected in the game |
|
|
|
|
|
* list folder. |
|
|
|
|
|
* |
|
|
|
|
|
* Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and |
|
|
|
|
|
* this function is fast enough to not stall the UI thread. If performance is an issue, it should |
|
|
|
|
|
* be moved to another thread and properly locked to prevent concurrency issues. |
|
|
|
|
|
* |
|
|
|
|
|
* @param dir folder to check for changes in |
|
|
|
|
|
* @param recursion 0 if recursion is disabled. Any positive number passed to this will add each |
|
|
|
|
|
* directory recursively to the watcher and will update the file list if any of the folders |
|
|
|
|
|
* change. The number determines how deep the recursion should traverse. |
|
|
|
|
|
*/ |
|
|
|
|
|
void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) { |
|
|
|
|
|
const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, |
|
|
|
|
|
const std::string& virtual_name) -> bool { |
|
|
|
|
|
std::string physical_name = directory + DIR_SEP + virtual_name; |
|
|
|
|
|
|
|
|
|
|
|
if (FileUtil::IsDirectory(physical_name)) { |
|
|
|
|
|
UpdateWatcherList(physical_name, recursion - 1); |
|
|
|
|
|
} |
|
|
|
|
|
return true; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
watcher.addPath(QString::fromStdString(dir)); |
|
|
|
|
|
if (recursion > 0) { |
|
|
|
|
|
FileUtil::ForeachDirectoryEntry(nullptr, dir, callback); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { |
|
|
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { |
|
|
const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, |
|
|
const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, |
|
|
const std::string& virtual_name) -> bool { |
|
|
const std::string& virtual_name) -> bool { |
|
|
@ -399,7 +380,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign |
|
|
if (stop_processing) |
|
|
if (stop_processing) |
|
|
return false; // Breaks the callback loop.
|
|
|
return false; // Breaks the callback loop.
|
|
|
|
|
|
|
|
|
if (!FileUtil::IsDirectory(physical_name) && HasSupportedFileExtension(physical_name)) { |
|
|
|
|
|
|
|
|
bool is_dir = FileUtil::IsDirectory(physical_name); |
|
|
|
|
|
if (!is_dir && HasSupportedFileExtension(physical_name)) { |
|
|
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); |
|
|
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); |
|
|
if (!loader) |
|
|
if (!loader) |
|
|
return true; |
|
|
return true; |
|
|
@ -416,7 +398,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign |
|
|
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), |
|
|
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), |
|
|
new GameListItemSize(FileUtil::GetSize(physical_name)), |
|
|
new GameListItemSize(FileUtil::GetSize(physical_name)), |
|
|
}); |
|
|
}); |
|
|
} else if (recursion > 0) { |
|
|
|
|
|
|
|
|
} else if (is_dir && recursion > 0) { |
|
|
|
|
|
watch_list.append(QString::fromStdString(physical_name)); |
|
|
AddFstEntriesToGameList(physical_name, recursion - 1); |
|
|
AddFstEntriesToGameList(physical_name, recursion - 1); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -428,8 +411,9 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign |
|
|
|
|
|
|
|
|
void GameListWorker::run() { |
|
|
void GameListWorker::run() { |
|
|
stop_processing = false; |
|
|
stop_processing = false; |
|
|
|
|
|
watch_list.append(dir_path); |
|
|
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); |
|
|
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); |
|
|
emit Finished(); |
|
|
|
|
|
|
|
|
emit Finished(watch_list); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void GameListWorker::Cancel() { |
|
|
void GameListWorker::Cancel() { |
|
|
|