From cd92452202ba4179281c83aaa9d3b6947788c9d8 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 28 Oct 2025 17:01:37 +0100 Subject: [PATCH] file watcher --- src/yuzu/game_list.cpp | 70 +++++++++++++++++++++++++++--------------- src/yuzu/game_list.h | 2 ++ 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 89bbb6078f..28e270d02b 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "yuzu/game_list.h" +#include #include #include #include @@ -13,20 +13,21 @@ #include #include #include +#include #include "common/common_types.h" #include "common/logging/log.h" #include "core/core.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" -#include "qt_common/util/game.h" +#include "core/hle/service/filesystem/filesystem.h" #include "qt_common/config/uisettings.h" +#include "qt_common/util/game.h" #include "yuzu/compatibility_list.h" +#include "yuzu/game_list.h" #include "yuzu/game_list_p.h" #include "yuzu/game_list_worker.h" #include "yuzu/main.h" #include "yuzu/util/controller_navigation.h" -#include -#include GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist_, QObject* parent) : QObject(parent), gamelist{gamelist_} {} @@ -318,7 +319,8 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, play_time_manager{play_time_manager_}, system{system_} { watcher = new QFileSystemWatcher(this); - connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); + connect(watcher, &QFileSystemWatcher::directoryChanged, this, + &GameList::OnWatchedDirectoryChanged); this->main_window = parent; layout = new QVBoxLayout; @@ -486,14 +488,21 @@ void GameList::DonePopulating(const QStringList& watch_list) { if (!watch_dirs.isEmpty()) { watcher->removePaths(watch_dirs); } + + QStringList all_watch_paths = watch_list; + for (const auto& dir : Settings::values.external_dirs) { + all_watch_paths.append(QString::fromStdString(dir)); + } + all_watch_paths.removeDuplicates(); + // 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)(static_cast(watch_list.size()), LIMIT_WATCH_DIRECTORIES); + int len = (std::min)(static_cast(all_watch_paths.size()), LIMIT_WATCH_DIRECTORIES); for (int i = 0; i < len; i += SLICE_SIZE) { - watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE)); + watcher->addPaths(all_watch_paths.mid(i, i + SLICE_SIZE)); QCoreApplication::processEvents(); } tree_view->setEnabled(true); @@ -619,26 +628,32 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri emit RemoveInstalledEntryRequested(program_id, QtCommon::Game::InstalledEntryType::Update); }); connect(remove_dlc, &QAction::triggered, [this, program_id]() { - emit RemoveInstalledEntryRequested(program_id, QtCommon::Game::InstalledEntryType::AddOnContent); + emit RemoveInstalledEntryRequested(program_id, + QtCommon::Game::InstalledEntryType::AddOnContent); }); connect(remove_gl_shader_cache, &QAction::triggered, [this, program_id, path]() { - emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::GlShaderCache, path); + emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::GlShaderCache, + path); }); connect(remove_vk_shader_cache, &QAction::triggered, [this, program_id, path]() { - emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::VkShaderCache, path); + emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::VkShaderCache, + path); }); connect(remove_shader_cache, &QAction::triggered, [this, program_id, path]() { - emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::AllShaderCache, path); + emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::AllShaderCache, + path); }); connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { - emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::CustomConfiguration, path); + emit RemoveFileRequested(program_id, + QtCommon::Game::GameListRemoveTarget::CustomConfiguration, path); }); connect(set_play_time, &QAction::triggered, [this, program_id]() { emit SetPlayTimeRequested(program_id); }); connect(remove_play_time_data, &QAction::triggered, [this, program_id]() { emit RemovePlayTimeRequested(program_id); }); connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { - emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::CacheStorage, path); + emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::CacheStorage, + path); }); connect(dump_romfs, &QAction::triggered, [this, program_id, path]() { emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::Normal); @@ -665,8 +680,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri connect(properties, &QAction::triggered, [this, path]() { emit OpenPerGameGeneralRequested(path); }); - connect(ryujinx, &QAction::triggered, [this, program_id]() { emit LinkToRyujinxRequested(program_id); - }); + connect(ryujinx, &QAction::triggered, + [this, program_id]() { emit LinkToRyujinxRequested(program_id); }); }; void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) { @@ -830,8 +845,7 @@ QStandardItemModel* GameList::GetModel() const { return item_model; } -void GameList::PopulateAsync(QVector& game_dirs) -{ +void GameList::PopulateAsync(QVector& game_dirs) { tree_view->setEnabled(false); // Update the columns in case UISettings has changed @@ -848,12 +862,8 @@ void GameList::PopulateAsync(QVector& game_dirs) item_model->removeRows(0, item_model->rowCount()); search_field->clear(); - current_worker = std::make_unique(vfs, - provider, - game_dirs, - compatibility_list, - play_time_manager, - system); + current_worker = std::make_unique(vfs, provider, game_dirs, compatibility_list, + play_time_manager, system); // Get events from the worker as data becomes available connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent, @@ -882,8 +892,7 @@ const QStringList GameList::supported_file_extensions = { QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"), QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; -void GameList::RefreshGameDirectory() -{ +void GameList::RefreshGameDirectory() { if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); PopulateAsync(UISettings::values.game_dirs); @@ -967,6 +976,17 @@ GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} GameListPlaceholder::~GameListPlaceholder() = default; +void GameList::OnWatchedDirectoryChanged(const QString& path) { + LOG_INFO(Frontend, "Change detected in watched directory {}. Reloading content.", + path.toStdString()); + + system.GetFileSystemController().RebuildExternalContentIndex(); + + QtCommon::Game::ResetMetadata(false); + + RefreshGameDirectory(); +} + void GameListPlaceholder::onUpdateThemedIcons() { image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); } diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 0b3ac7fcde..7c369583f5 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -81,6 +81,8 @@ public: void LoadCompatibilityList(); void PopulateAsync(QVector& game_dirs); + void OnWatchedDirectoryChanged(const QString& path); + void SaveInterfaceLayout(); void LoadInterfaceLayout();