From a156697822c4635c038a47df27dd7a2b563848e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 26 Mar 2025 10:00:27 +0100 Subject: [PATCH] Break out the "Recent ISOs" code into its own file so it's more practical to work on it --- CMakeLists.txt | 2 + Common/File/FileUtil.cpp | 2 +- Core/Config.cpp | 158 ++++------------------------ Core/Config.h | 4 +- Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 ++ Core/Util/RecentFiles.cpp | 155 +++++++++++++++++++++++++++ Core/Util/RecentFiles.h | 18 ++++ UWP/CoreUWP/CoreUWP.vcxproj | 4 +- UWP/CoreUWP/CoreUWP.vcxproj.filters | 8 +- android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 12 files changed, 217 insertions(+), 144 deletions(-) create mode 100644 Core/Util/RecentFiles.cpp create mode 100644 Core/Util/RecentFiles.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fa8e556a41..25bde24f96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2425,6 +2425,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Util/BlockAllocator.h Core/Util/PPGeDraw.cpp Core/Util/PPGeDraw.h + Core/Util/RecentFiles.cpp + Core/Util/RecentFiles.h ${GPU_SOURCES} ext/disarm.cpp ext/disarm.h diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index 975adb648e..882fbf4317 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -328,7 +328,7 @@ std::string ResolvePath(const std::string &path) { } if (Android_IsContentUri(path)) { - // Nothing to do? + // Nothing to do? We consider these to only have one canonical form. return path; } diff --git a/Core/Config.cpp b/Core/Config.cpp index f0baff4c42..7b1d0d150d 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -52,11 +52,12 @@ #include "Core/Config.h" #include "Core/ConfigSettings.h" #include "Core/ConfigValues.h" -#include "Core/Loaders.h" #include "Core/KeyMap.h" #include "Core/System.h" #include "Core/HLE/sceUtility.h" #include "Core/Instance.h" +#include "Core/Util/RecentFiles.h" + #include "GPU/Common/FramebufferManagerCommon.h" // TODO: Find a better place for this. @@ -66,24 +67,13 @@ Config g_Config; static bool jitForcedOff; -// Not in Config.h because it's #included a lot. -struct ConfigPrivate { - std::mutex recentIsosLock; - std::mutex recentIsosThreadLock; - std::thread recentIsosThread; - bool recentIsosThreadPending = false; - - void ResetRecentIsosThread(); - void SetRecentIsosThread(std::function f); -}; - #ifdef _DEBUG static const char * const logSectionName = "LogDebug"; #else static const char * const logSectionName = "Log"; #endif -static bool TryUpdateSavedPath(Path *path); +bool TryUpdateSavedPath(Path *path); std::string GPUBackendToString(GPUBackend backend) { switch (backend) { @@ -1091,30 +1081,13 @@ static void IterateSettings(std::function fu } } -void ConfigPrivate::ResetRecentIsosThread() { - std::lock_guard guard(recentIsosThreadLock); - if (recentIsosThreadPending && recentIsosThread.joinable()) - recentIsosThread.join(); -} - -void ConfigPrivate::SetRecentIsosThread(std::function f) { - std::lock_guard guard(recentIsosThreadLock); - if (recentIsosThreadPending && recentIsosThread.joinable()) - recentIsosThread.join(); - recentIsosThread = std::thread(f); - recentIsosThreadPending = true; -} - -Config::Config() { - private_ = new ConfigPrivate(); -} +Config::Config() {} Config::~Config() { if (bUpdatedInstanceCounter) { ShutdownInstanceCounter(); } - private_->ResetRecentIsosThread(); - delete private_; + ResetRecentIsosThread(); } void Config::LoadLangValuesMapping() { @@ -1269,18 +1242,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { } if (iMaxRecent > 0) { - private_->ResetRecentIsosThread(); - std::lock_guard guard(private_->recentIsosLock); - recentIsos.clear(); - for (int i = 0; i < iMaxRecent; i++) { - char keyName[64]; - std::string fileName; - - snprintf(keyName, sizeof(keyName), "FileName%d", i); - if (recent->Get(keyName, &fileName, "") && !fileName.empty()) { - recentIsos.push_back(fileName); - } - } + LoadRecentIsos(recent, iMaxRecent); } // Time tracking @@ -1417,18 +1379,7 @@ bool Config::Save(const char *saveReason) { Section *recent = iniFile.GetOrCreateSection("Recent"); recent->Set("MaxRecent", iMaxRecent); - - private_->ResetRecentIsosThread(); - for (int i = 0; i < iMaxRecent; i++) { - char keyName[64]; - snprintf(keyName, sizeof(keyName), "FileName%d", i); - std::lock_guard guard(private_->recentIsosLock); - if (i < (int)recentIsos.size()) { - recent->Set(keyName, recentIsos[i]); - } else { - recent->Delete(keyName); // delete the nonexisting FileName - } - } + SaveRecentIsos(recent, iMaxRecent); Section *pinnedPaths = iniFile.GetOrCreateSection("PinnedPaths"); pinnedPaths->Clear(); @@ -1621,44 +1572,30 @@ void Config::DismissUpgrade() { g_Config.dismissedVersion = g_Config.upgradeVersion; } -void Config::AddRecent(const std::string &file) { +void Config::AddRecent(const std::string &filename) { // Don't bother with this if the user disabled recents (it's -1). if (iMaxRecent <= 0) return; // We'll add it back below. This makes sure it's at the front, and only once. - RemoveRecent(file); - - private_->ResetRecentIsosThread(); - std::lock_guard guard(private_->recentIsosLock); - const std::string filename = File::ResolvePath(file); - recentIsos.insert(recentIsos.begin(), filename); - if ((int)recentIsos.size() > iMaxRecent) - recentIsos.resize(iMaxRecent); + const std::string resolvedFilename = File::ResolvePath(filename); + AddRecentResolved(resolvedFilename, iMaxRecent); } -void Config::RemoveRecent(const std::string &file) { - // Don't bother with this if the user disabled recents (it's -1). - if (iMaxRecent <= 0) +void Config::RemoveRecent(const std::string &filename) { + if (iMaxRecent <= 0) { return; + } - private_->ResetRecentIsosThread(); - std::lock_guard guard(private_->recentIsosLock); - - const std::string filename = File::ResolvePath(file); - auto iter = std::remove_if(recentIsos.begin(), recentIsos.end(), [filename](const auto &str) { - const std::string recent = File::ResolvePath(str); - return filename == recent; - }); - // remove_if is weird. - recentIsos.erase(iter, recentIsos.end()); + std::string resolvedFilename = File::ResolvePath(filename); + RemoveRecentResolved(resolvedFilename); } // On iOS, the path to the app documents directory changes on each launch. // Example path: // /var/mobile/Containers/Data/Application/0E0E89DE-8D8E-485A-860C-700D8BC87B86/Documents/PSP/GAME/SuicideBarbie // The GUID part changes on each launch. -static bool TryUpdateSavedPath(Path *path) { +bool TryUpdateSavedPath(Path *path) { #if PPSSPP_PLATFORM(IOS) INFO_LOG(Log::Loader, "Original path: %s", path->c_str()); std::string pathStr = path->ToString(); @@ -1683,74 +1620,19 @@ static bool TryUpdateSavedPath(Path *path) { } void Config::CleanRecent() { - private_->SetRecentIsosThread([this] { - SetCurrentThreadName("RecentISOs"); - - AndroidJNIThreadContext jniContext; // destructor detaches - - double startTime = time_now_d(); - - std::lock_guard guard(private_->recentIsosLock); - std::vector cleanedRecent; - if (recentIsos.empty()) { - INFO_LOG(Log::Loader, "No recents list found."); - } - - for (size_t i = 0; i < recentIsos.size(); i++) { - bool exists = false; - Path path = Path(recentIsos[i]); - switch (path.Type()) { - case PathType::CONTENT_URI: - case PathType::NATIVE: - exists = File::Exists(path); - if (!exists) { - if (TryUpdateSavedPath(&path)) { - exists = File::Exists(path); - INFO_LOG(Log::Loader, "Exists=%d when checking updated path: %s", exists, path.c_str()); - } - } - break; - default: - FileLoader *loader = ConstructFileLoader(path); - exists = loader->ExistsFast(); - delete loader; - break; - } - - if (exists) { - std::string pathStr = path.ToString(); - // Make sure we don't have any redundant items. - auto duplicate = std::find(cleanedRecent.begin(), cleanedRecent.end(), pathStr); - if (duplicate == cleanedRecent.end()) { - cleanedRecent.push_back(pathStr); - } - } else { - DEBUG_LOG(Log::Loader, "Removed %s from recent. errno=%d", path.c_str(), errno); - } - } - - double recentTime = time_now_d() - startTime; - if (recentTime > 0.1) { - INFO_LOG(Log::System, "CleanRecent took %0.2f", recentTime); - } - recentIsos = cleanedRecent; - }); + CleanRecentIsos(); } std::vector Config::RecentIsos() const { - std::lock_guard guard(private_->recentIsosLock); - return recentIsos; + return GetRecentIsos(); } bool Config::HasRecentIsos() const { - std::lock_guard guard(private_->recentIsosLock); - return !recentIsos.empty(); + return ::HasRecentIsos(); } void Config::ClearRecentIsos() { - private_->ResetRecentIsosThread(); - std::lock_guard guard(private_->recentIsosLock); - recentIsos.clear(); + ::CleanRecentIsos(); } void Config::SetSearchPath(const Path &searchPath) { diff --git a/Core/Config.h b/Core/Config.h index 9802dea07f..9c4c6ab3ac 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -35,7 +35,6 @@ namespace http { } struct UrlEncoder; -struct ConfigPrivate; class Section; @@ -670,7 +669,6 @@ private: bool reload_ = false; std::string gameId_; std::string gameIdTitle_; - std::vector recentIsos; std::map, std::less<>> langValuesMapping_; PlayTimeTracker playTimeTracker_; Path iniFilename_; @@ -679,10 +677,10 @@ private: Path appendedConfigFileName_; // A set make more sense, but won't have many entry, and I dont want to include the whole std::set header here std::vector appendedConfigUpdatedGames_; - ConfigPrivate *private_ = nullptr; }; std::string CreateRandMAC(); +bool TryUpdateSavedPath(Path *path); // TODO: Find a better place for this. extern http::RequestManager g_DownloadManager; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 869d7efa44..4c93ee4e8e 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -1120,6 +1120,7 @@ AnySuitable AnySuitable + @@ -1478,6 +1479,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index b92c083ea8..0f4a8298f9 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1339,6 +1339,9 @@ Util + + Util + @@ -2160,6 +2163,9 @@ Util + + Util + diff --git a/Core/Util/RecentFiles.cpp b/Core/Util/RecentFiles.cpp new file mode 100644 index 0000000000..c46142829b --- /dev/null +++ b/Core/Util/RecentFiles.cpp @@ -0,0 +1,155 @@ +#include +#include +#include + +#include "Common/File/FileUtil.h" +#include "Common/Thread/ThreadUtil.h" +#include "Common/Log.h" +#include "Common/TimeUtil.h" +#include "Core/Loaders.h" +#include "Core/Util/RecentFiles.h" +#include "Core/Config.h" + +// Not in Config.h because it's #included a lot. +std::mutex recentIsosLock; +std::mutex recentIsosThreadLock; +std::thread recentIsosThread; +bool recentIsosThreadPending = false; + +std::vector recentIsos; + +std::vector GetRecentIsos() { + std::lock_guard guard(recentIsosLock); + return recentIsos; +} + +bool HasRecentIsos() { + std::lock_guard guard(recentIsosLock); + return !recentIsos.empty(); +} + +void ClearRecentIsos() { + ResetRecentIsosThread(); + std::lock_guard guard(recentIsosLock); + recentIsos.clear(); +} + +void ResetRecentIsosThread() { + std::lock_guard guard(recentIsosThreadLock); + if (recentIsosThreadPending && recentIsosThread.joinable()) + recentIsosThread.join(); +} + +void SetRecentIsosThread(std::function f) { + std::lock_guard guard(recentIsosThreadLock); + if (recentIsosThreadPending && recentIsosThread.joinable()) + recentIsosThread.join(); + recentIsosThread = std::thread(f); + recentIsosThreadPending = true; +} + +void LoadRecentIsos(const Section *recent, int maxRecent) { + ResetRecentIsosThread(); + std::lock_guard guard(recentIsosLock); + recentIsos.clear(); + for (int i = 0; i < maxRecent; i++) { + char keyName[64]; + std::string fileName; + + snprintf(keyName, sizeof(keyName), "FileName%d", i); + if (recent->Get(keyName, &fileName, "") && !fileName.empty()) { + recentIsos.push_back(fileName); + } + } +} + +void SaveRecentIsos(Section *recent, int maxRecent) { + ResetRecentIsosThread(); + for (int i = 0; i < maxRecent; i++) { + char keyName[64]; + snprintf(keyName, sizeof(keyName), "FileName%d", i); + std::lock_guard guard(recentIsosLock); + if (i < (int)recentIsos.size()) { + recent->Set(keyName, recentIsos[i]); + } else { + recent->Delete(keyName); // delete the nonexisting FileName + } + } +} + +void AddRecentResolved(const std::string &resolvedFilename, int maxRecent) { + RemoveRecentResolved(resolvedFilename); + + ResetRecentIsosThread(); + std::lock_guard guard(recentIsosLock); + recentIsos.insert(recentIsos.begin(), resolvedFilename); + if ((int)recentIsos.size() > maxRecent) + recentIsos.resize(maxRecent); +} + +void RemoveRecentResolved(const std::string &resolvedFilename) { + ResetRecentIsosThread(); + + std::lock_guard guard(recentIsosLock); + auto iter = std::remove_if(recentIsos.begin(), recentIsos.end(), [resolvedFilename](const auto &str) { + const std::string recent = File::ResolvePath(str); + return resolvedFilename == recent; + }); + // remove_if is weird. + recentIsos.erase(iter, recentIsos.end()); +} + +void CleanRecentIsos() { + SetRecentIsosThread([] { + SetCurrentThreadName("RecentISOs"); + + AndroidJNIThreadContext jniContext; // destructor detaches + + double startTime = time_now_d(); + + std::lock_guard guard(recentIsosLock); + std::vector cleanedRecent; + if (recentIsos.empty()) { + INFO_LOG(Log::Loader, "No recents list found."); + } + + for (size_t i = 0; i < recentIsos.size(); i++) { + bool exists = false; + Path path = Path(recentIsos[i]); + switch (path.Type()) { + case PathType::CONTENT_URI: + case PathType::NATIVE: + exists = File::Exists(path); + if (!exists) { + if (TryUpdateSavedPath(&path)) { + exists = File::Exists(path); + INFO_LOG(Log::Loader, "Exists=%d when checking updated path: %s", exists, path.c_str()); + } + } + break; + default: + FileLoader *loader = ConstructFileLoader(path); + exists = loader->ExistsFast(); + delete loader; + break; + } + + if (exists) { + std::string pathStr = path.ToString(); + // Make sure we don't have any redundant items. + auto duplicate = std::find(cleanedRecent.begin(), cleanedRecent.end(), pathStr); + if (duplicate == cleanedRecent.end()) { + cleanedRecent.push_back(pathStr); + } + } else { + DEBUG_LOG(Log::Loader, "Removed %s from recent. errno=%d", path.c_str(), errno); + } + } + + double recentTime = time_now_d() - startTime; + if (recentTime > 0.1) { + INFO_LOG(Log::System, "CleanRecent took %0.2f", recentTime); + } + recentIsos = cleanedRecent; + }); +} diff --git a/Core/Util/RecentFiles.h b/Core/Util/RecentFiles.h new file mode 100644 index 0000000000..9f3d03ae4f --- /dev/null +++ b/Core/Util/RecentFiles.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +#include "Common/Data/Format/IniFile.h" + +void ResetRecentIsosThread(); +void SetRecentIsosThread(std::function f); +void LoadRecentIsos(const Section *recent, int maxRecent); +void SaveRecentIsos(Section *recent, int maxRecent); +void AddRecentResolved(const std::string &resolvedFilename, int maxRecent); +void RemoveRecentResolved(const std::string &resolvedFilename); +void CleanRecentIsos(); +std::vector GetRecentIsos(); +bool HasRecentIsos(); +void ClearRecentIsos(); diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index b4ab643637..5d59554e4d 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -335,6 +335,7 @@ + @@ -633,6 +634,7 @@ + @@ -1038,4 +1040,4 @@ - + \ No newline at end of file diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index df3f272763..cf6274c2bb 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -1234,6 +1234,9 @@ Util + + Util + @@ -1952,10 +1955,13 @@ Util + + Util + Ext\gason - + \ No newline at end of file diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 2c80a248e2..f0b1d905a7 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -759,6 +759,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/Util/GameManager.cpp \ $(SRC)/Core/Util/BlockAllocator.cpp \ $(SRC)/Core/Util/PPGeDraw.cpp \ + $(SRC)/Core/Util/RecentFiles.cpp \ $(SRC)/git-version.cpp LOCAL_MODULE := ppsspp_core diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 8b182994c1..d71adc9ce1 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -828,6 +828,7 @@ SOURCES_CXX += \ $(COREDIR)/Util/BlockAllocator.cpp \ $(COREDIR)/Util/MemStick.cpp \ $(COREDIR)/Util/PPGeDraw.cpp \ + $(COREDIR)/Util/RecentFiles.cpp \ $(COREDIR)/Util/AudioFormat.cpp \ $(COREDIR)/Util/PortManager.cpp \ $(CORE_DIR)/UI/GameInfoCache.cpp