From b0ec5792416dc87c1f93efc257e40bfdd10411e6 Mon Sep 17 00:00:00 2001 From: crueter Date: Thu, 5 Feb 2026 16:27:57 -0500 Subject: [PATCH] Add live reload for external content Signed-off-by: crueter --- src/qt_common/util/game.cpp | 14 +++++--------- src/yuzu/game_list.cpp | 34 ++++++++++++++++++++++++++++++---- src/yuzu/game_list.h | 3 +++ src/yuzu/game_list_worker.cpp | 4 ---- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/qt_common/util/game.cpp b/src/qt_common/util/game.cpp index e34a388993..cb4e8ee8ca 100644 --- a/src/qt_common/util/game.cpp +++ b/src/qt_common/util/game.cpp @@ -373,27 +373,23 @@ void RemoveCacheStorage(u64 program_id) } // Metadata // -void ResetMetadata(bool show_message) -{ +void ResetMetadata(bool show_message) { const QString title = tr("Reset Metadata Cache"); - if (!Common::FS::Exists(Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) - / "game_list/")) { + if (!Common::FS::Exists(Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / + "game_list/")) { if (show_message) - QtCommon::Frontend::Warning(rootObject, - title, + QtCommon::Frontend::Warning(rootObject, title, tr("The metadata cache is already empty.")); } else if (Common::FS::RemoveDirRecursively( Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "game_list")) { if (show_message) - QtCommon::Frontend::Information(rootObject, - title, + QtCommon::Frontend::Information(rootObject, title, tr("The operation completed successfully.")); UISettings::values.is_game_list_reload_pending.exchange(true); } else { if (show_message) QtCommon::Frontend::Warning( - title, tr("The metadata cache couldn't be deleted. It might be in use or non-existent.")); } diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 993e2c8674..d206ab096b 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -16,11 +16,13 @@ #include #include #include +#include #include #include #include #include "common/common_types.h" #include "common/logging/log.h" +#include "common/settings.h" #include "core/core.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" @@ -326,6 +328,10 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid watcher = new QFileSystemWatcher(this); connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); + external_watcher = new QFileSystemWatcher(this); + ResetExternalWatcher(); + connect(external_watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshExternalContent); + this->main_window = parent; layout = new QVBoxLayout; tree_view = new QTreeView; @@ -920,18 +926,38 @@ const QStringList GameList::supported_file_extensions = { void GameList::RefreshGameDirectory() { + // Reset the externals watcher whenever the game list is reloaded, + // primarily ensures that new titles and external dirs are caught. + ResetExternalWatcher(); + if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); - // TODO: If you force reset metadata here, live updates to the external directories DO get tracked. - // This is not correct however. Game list metadata in general is kind of a mess, but ideally this isn't the - // behavior as-is + PopulateAsync(UISettings::values.game_dirs); + } +} - // QtCommon::Game::ResetMetadata(false); +void GameList::RefreshExternalContent() { + // TODO: Explore the possibility of only resetting the metadata cache for that specific game. + if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { + LOG_INFO(Frontend, "External content directory changed. Clearing metadata cache."); + QtCommon::Game::ResetMetadata(false); + QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); PopulateAsync(UISettings::values.game_dirs); } } +void GameList::ResetExternalWatcher() { + auto watch_dirs = external_watcher->directories(); + if (!watch_dirs.isEmpty()) { + external_watcher->removePaths(watch_dirs); + } + + for (const std::string &dir : Settings::values.external_content_dirs) { + external_watcher->addPath(QString::fromStdString(dir)); + } +} + void GameList::ToggleFavorite(u64 program_id) { if (!UISettings::values.favorited_ids.contains(program_id)) { tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 293a46a4f2..9b00e270cd 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -94,6 +94,8 @@ public: public slots: void RefreshGameDirectory(); + void RefreshExternalContent(); + void ResetExternalWatcher(); signals: void BootGame(const QString& game_path, StartGameType type); @@ -160,6 +162,7 @@ private: QStandardItemModel* item_model = nullptr; std::unique_ptr current_worker; QFileSystemWatcher* watcher = nullptr; + QFileSystemWatcher* external_watcher = nullptr; ControllerNavigation* controller_navigation = nullptr; CompatibilityList compatibility_list; diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index d095e039c5..131d6e6db4 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -510,10 +510,6 @@ void GameListWorker::run() { } } - for (const std::string &dir : Settings::values.external_content_dirs) { - watch_list << QString::fromStdString(dir); - } - RecordEvent([this](GameList* game_list) { game_list->DonePopulating(watch_list); }); processing_completed.Set(); }