diff --git a/Common/System/System.h b/Common/System/System.h index 700ea141a4..8bc5d71b98 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -279,6 +279,7 @@ enum class UIMessage { GAMESETTINGS_SEARCH, SAVEDATA_SEARCH, RESTART_GRAPHICS, + RECENT_FILES_CHANGED, }; std::string System_GetProperty(SystemProperty prop); diff --git a/Core/Config.cpp b/Core/Config.cpp index 028bcdcf43..2ea788dc48 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1087,7 +1087,6 @@ Config::~Config() { if (bUpdatedInstanceCounter) { ShutdownInstanceCounter(); } - ResetRecentIsosThread(); } void Config::LoadLangValuesMapping() { @@ -1242,7 +1241,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { } if (iMaxRecent > 0) { - LoadRecentIsos(recent, iMaxRecent); + g_recentFiles.Load(recent, iMaxRecent); } // Time tracking @@ -1339,7 +1338,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { loadGameConfig(gameId_, gameIdTitle_); } - CleanRecent(); + g_recentFiles.Clean(); PostLoadCleanup(false); @@ -1362,7 +1361,7 @@ bool Config::Save(const char *saveReason) { PreSaveCleanup(false); - CleanRecent(); + g_recentFiles.Clean(); IniFile iniFile; if (!iniFile.Load(iniFilename_)) { WARN_LOG(Log::Loader, "Likely saving config for first time - couldn't read ini '%s'", iniFilename_.c_str()); @@ -1379,7 +1378,7 @@ bool Config::Save(const char *saveReason) { Section *recent = iniFile.GetOrCreateSection("Recent"); recent->Set("MaxRecent", iMaxRecent); - SaveRecentIsos(recent, iMaxRecent); + g_recentFiles.Save(recent, iMaxRecent); Section *pinnedPaths = iniFile.GetOrCreateSection("PinnedPaths"); pinnedPaths->Clear(); @@ -1572,23 +1571,6 @@ void Config::DismissUpgrade() { g_Config.dismissedVersion = g_Config.upgradeVersion; } -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. - ::AddRecent(filename, iMaxRecent); -} - -void Config::RemoveRecent(const std::string &filename) { - if (iMaxRecent <= 0) { - return; - } - - ::RemoveRecent(filename); -} - // 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 @@ -1617,22 +1599,6 @@ bool TryUpdateSavedPath(Path *path) { #endif } -void Config::CleanRecent() { - CleanRecentIsos(); -} - -std::vector Config::RecentIsos() const { - return GetRecentIsos(); -} - -bool Config::HasRecentIsos() const { - return ::HasRecentIsos(); -} - -void Config::ClearRecentIsos() { - ::CleanRecentIsos(); -} - void Config::SetSearchPath(const Path &searchPath) { searchPath_ = searchPath; } @@ -1686,7 +1652,7 @@ void Config::RestoreDefaults(RestoreSettingsBits whatToRestore) { } if (whatToRestore & RestoreSettingsBits::RECENT) { - ClearRecentIsos(); + g_recentFiles.Clear(); currentDirectory = defaultCurrentDirectory; } } diff --git a/Core/Config.h b/Core/Config.h index 9c4c6ab3ac..54bc7dcb08 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -620,11 +620,6 @@ public: void UpdateIniLocation(const char *iniFileName = nullptr, const char *controllerIniFilename = nullptr); - // Utility functions for "recent" management - void AddRecent(const std::string &file); - void RemoveRecent(const std::string &file); - void CleanRecent(); - static void DownloadCompletedCallback(http::Request &download); void DismissUpgrade(); @@ -642,10 +637,6 @@ public: return bFullScreen; } - std::vector RecentIsos() const; - bool HasRecentIsos() const; - void ClearRecentIsos(); - const std::map, std::less<>> &GetLangValuesMapping(); bool LoadAppendedConfig(); void SetAppendedConfigIni(const Path &path); diff --git a/Core/System.cpp b/Core/System.cpp index 541744fbd9..1ee819ede8 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -62,13 +62,12 @@ #include "Core/PSPLoaders.h" #include "Core/ELF/ParamSFO.h" #include "Core/SaveState.h" -#include "Common/File/FileUtil.h" +#include "Core/Util/RecentFiles.h" #include "Common/StringUtils.h" #include "Common/ExceptionHandlerSetup.h" #include "GPU/GPUCommon.h" #include "GPU/Debugger/Playback.h" #include "GPU/Debugger/RecordFormat.h" -#include "Core/RetroAchievements.h" enum CPUThreadState { CPU_THREAD_NOT_RUNNING, @@ -316,7 +315,7 @@ bool CPU_Init(std::string *errorString, FileLoader *loadedFile, IdentifiedFileTy } if (g_CoreParameter.updateRecent) { - g_Config.AddRecent(g_CoreParameter.fileToStart.ToString()); + g_recentFiles.Add(g_CoreParameter.fileToStart.ToString()); } InstallExceptionHandler(&Memory::HandleFault); diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index 2b74fe82a4..11e633f635 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -47,6 +47,7 @@ #include "Core/System.h" #include "Core/FileSystems/ISOFileSystem.h" #include "Core/Util/GameManager.h" +#include "Core/Util/RecentFiles.h" #include "Common/Data/Text/I18n.h" GameManager g_GameManager; @@ -189,7 +190,7 @@ void GameManager::Update() { installThread_.join(); } if (cleanRecentsAfter_.exchange(false)) { - g_Config.CleanRecent(); + g_recentFiles.Clean(); } } } @@ -536,7 +537,7 @@ bool GameManager::DetectTexturePackDest(struct zip *z, int iniIndex, Path &dest) std::string gameID = games.begin()->first; if (games.size() > 1) { // Check for any supported game on their recent list and use that instead. - for (const std::string &path : g_Config.RecentIsos()) { + for (const std::string &path : g_recentFiles.GetRecentFiles()) { std::string recentID = GetGameID(Path(path)); if (games.find(recentID) != games.end()) { gameID = recentID; diff --git a/Core/Util/RecentFiles.cpp b/Core/Util/RecentFiles.cpp index a8666f9baf..1cc4acc4df 100644 --- a/Core/Util/RecentFiles.cpp +++ b/Core/Util/RecentFiles.cpp @@ -10,37 +10,54 @@ #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; +RecentFilesManager g_recentFiles; -std::vector recentIsos; +RecentFilesManager::RecentFilesManager() { -std::vector GetRecentIsos() { +} + +RecentFilesManager::~RecentFilesManager() { + ResetThread(); +} + +std::vector RecentFilesManager::GetRecentFiles() const { std::lock_guard guard(recentIsosLock); return recentIsos; } -bool HasRecentIsos() { +bool RecentFilesManager::ContainsFile(const std::string &filename) { + if (g_Config.iMaxRecent <= 0) + return false; + // Unfortunately this resolve needs to be done synchonously. + std::string resolvedFilename = File::ResolvePath(filename); + + std::lock_guard guard(recentIsosLock); + for (const auto & file : recentIsos) { + if (file == resolvedFilename) { + return true; + } + } + return false; +} + +bool RecentFilesManager::HasAny() const { std::lock_guard guard(recentIsosLock); return !recentIsos.empty(); } -void ClearRecentIsos() { - ResetRecentIsosThread(); +void RecentFilesManager::Clear() { + ResetThread(); std::lock_guard guard(recentIsosLock); recentIsos.clear(); } -void ResetRecentIsosThread() { +void RecentFilesManager::ResetThread() { std::lock_guard guard(recentIsosThreadLock); if (recentIsosThreadPending && recentIsosThread.joinable()) recentIsosThread.join(); } -void SetRecentIsosThread(std::function f) { +void RecentFilesManager::SetThread(std::function f) { std::lock_guard guard(recentIsosThreadLock); if (recentIsosThreadPending && recentIsosThread.joinable()) recentIsosThread.join(); @@ -48,8 +65,8 @@ void SetRecentIsosThread(std::function f) { recentIsosThreadPending = true; } -void LoadRecentIsos(const Section *recent, int maxRecent) { - ResetRecentIsosThread(); +void RecentFilesManager::Load(const Section *recent, int maxRecent) { + ResetThread(); std::vector newRecent; for (int i = 0; i < maxRecent; i++) { @@ -66,8 +83,8 @@ void LoadRecentIsos(const Section *recent, int maxRecent) { recentIsos = newRecent; } -void SaveRecentIsos(Section *recent, int maxRecent) { - ResetRecentIsosThread(); +void RecentFilesManager::Save(Section *recent, int maxRecent) { + ResetThread(); std::vector recentCopy; { @@ -86,8 +103,8 @@ void SaveRecentIsos(Section *recent, int maxRecent) { } } -void RemoveRecentResolved(const std::string &resolvedFilename) { - ResetRecentIsosThread(); +void RecentFilesManager::RemoveResolved(const std::string &resolvedFilename) { + ResetThread(); std::lock_guard guard(recentIsosLock); auto iter = std::remove_if(recentIsos.begin(), recentIsos.end(), [resolvedFilename](const auto &str) { @@ -97,24 +114,32 @@ void RemoveRecentResolved(const std::string &resolvedFilename) { recentIsos.erase(iter, recentIsos.end()); } -void AddRecent(const std::string &filename, int maxRecent) { - std::string resolvedFilename = File::ResolvePath(filename); - RemoveRecentResolved(resolvedFilename); +void RecentFilesManager::Add(const std::string &filename) { + if (g_Config.iMaxRecent <= 0) { + return; + } - ResetRecentIsosThread(); + std::string resolvedFilename = File::ResolvePath(filename); + RemoveResolved(resolvedFilename); + + ResetThread(); std::lock_guard guard(recentIsosLock); recentIsos.insert(recentIsos.begin(), resolvedFilename); - if ((int)recentIsos.size() > maxRecent) - recentIsos.resize(maxRecent); + if ((int)recentIsos.size() > g_Config.iMaxRecent) + recentIsos.resize(g_Config.iMaxRecent); } -void RemoveRecent(const std::string &filename) { +void RecentFilesManager::Remove(const std::string &filename) { + if (g_Config.iMaxRecent <= 0) { + return; + } + std::string resolvedFilename = File::ResolvePath(filename); - RemoveRecentResolved(resolvedFilename); + RemoveResolved(resolvedFilename); } -void CleanRecentIsos() { - SetRecentIsosThread([] { +void RecentFilesManager::Clean() { + SetThread([this] { SetCurrentThreadName("RecentISOs"); AndroidJNIThreadContext jniContext; // destructor detaches diff --git a/Core/Util/RecentFiles.h b/Core/Util/RecentFiles.h index bca48f0d32..6d27f7d7b6 100644 --- a/Core/Util/RecentFiles.h +++ b/Core/Util/RecentFiles.h @@ -3,16 +3,39 @@ #include #include #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 AddRecent(const std::string &resolvedFilename, int maxRecent); -void RemoveRecent(const std::string &resolvedFilename); -void CleanRecentIsos(); -std::vector GetRecentIsos(); -bool HasRecentIsos(); -void ClearRecentIsos(); +// You'd think this would be simple enough to not require a manager, but we +// want to provide a clean list with non-existent files removed, while never +// blocking the main thread. It does get a bit complex. +class RecentFilesManager { +public: + RecentFilesManager(); + ~RecentFilesManager(); + + void Load(const Section *recent, int maxRecent); + void Save(Section *recent, int maxRecent); + void Add(const std::string &filename); + void Remove(const std::string &filename); + void Clean(); + bool HasAny() const; + void Clear(); + bool ContainsFile(const std::string &filename); + + std::vector GetRecentFiles() const; +private: + void ResetThread(); + void SetThread(std::function f); + void RemoveResolved(const std::string &resolvedFilename); + std::vector recentIsos; + mutable std::mutex recentIsosLock; + mutable std::mutex recentIsosThreadLock; + mutable std::thread recentIsosThread; + bool recentIsosThreadPending = false; +}; + +// Singleton, don't make more. +extern RecentFilesManager g_recentFiles; diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index 43d415ed62..1a36a45b91 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -31,6 +31,7 @@ #include "Common/File/VFS/VFS.h" #include "Common/TimeUtil.h" #include "Common/StringUtils.h" +#include "Core/Util/RecentFiles.h" #include "Core/Config.h" #include "Core/Debugger/WebSocket.h" #include "Core/WebServer.h" @@ -172,7 +173,7 @@ static std::string RemotePathForRecent(const std::string &filename) { static Path LocalFromRemotePath(const std::string &path) { switch ((RemoteISOShareType)g_Config.iRemoteISOShareType) { case RemoteISOShareType::RECENT: - for (const std::string &filename : g_Config.RecentIsos()) { + for (const std::string &filename : g_recentFiles.GetRecentFiles()) { std::string basename = RemotePathForRecent(filename); if (basename == path) { return Path(filename); @@ -269,7 +270,7 @@ static void HandleListing(const http::ServerRequest &request) { switch ((RemoteISOShareType)g_Config.iRemoteISOShareType) { case RemoteISOShareType::RECENT: // List the current discs in their recent order. - for (const std::string &filename : g_Config.RecentIsos()) { + for (const std::string &filename : g_recentFiles.GetRecentFiles()) { std::string basename = RemotePathForRecent(filename); if (!basename.empty()) { request.Out()->Printf("%s\n", basename.c_str()); diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index a786f0783c..eb8b9ff4ab 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -41,6 +41,7 @@ #include "Core/System.h" #include "Core/Loaders.h" #include "Core/Util/GameManager.h" +#include "Core/Util/RecentFiles.h" #include "Core/Config.h" #include "UI/GameInfoCache.h" @@ -81,7 +82,7 @@ bool GameInfo::Delete() { Path fileToRemove = filePath_; INFO_LOG(Log::System, "Deleting file %s", fileToRemove.c_str()); File::Delete(fileToRemove); - g_Config.RemoveRecent(filePath_.ToString()); + g_recentFiles.Remove(filePath_.ToString()); return true; } case IdentifiedFileType::PSP_PBP_DIRECTORY: @@ -94,7 +95,7 @@ bool GameInfo::Delete() { ERROR_LOG(Log::System, "Failed to delete file"); return false; } - g_Config.CleanRecent(); + g_recentFiles.Clean(); return true; } case IdentifiedFileType::PSP_ELF: @@ -109,7 +110,7 @@ bool GameInfo::Delete() { const Path &fileToRemove = filePath_; INFO_LOG(Log::System, "Deleting file %s", fileToRemove.c_str()); File::Delete(fileToRemove); - g_Config.RemoveRecent(filePath_.ToString()); + g_recentFiles.Remove(filePath_.ToString()); return true; } diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 2c060bf317..284e2868ae 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -39,6 +39,7 @@ #include "Core/Loaders.h" #include "Core/Util/GameDB.h" #include "Core/HLE/Plugins.h" +#include "Core/Util/RecentFiles.h" #include "UI/OnScreenDisplay.h" #include "UI/CwCheatScreen.h" #include "UI/EmuScreen.h" @@ -266,7 +267,7 @@ void GameScreen::CreateViews() { }); } - if (isRecentGame(gamePath_)) { + if (g_recentFiles.ContainsFile(gamePath_.ToString())) { Choice *removeButton = rightColumnItems->Add(AddOtherChoice(new Choice(ga->T("Remove From Recent")))); removeButton->OnClick.Handle(this, &GameScreen::OnRemoveFromRecent); if (inGame_) { @@ -577,21 +578,8 @@ void GameScreen::CallbackDeleteGame(bool yes) { } } -bool GameScreen::isRecentGame(const Path &gamePath) { - if (g_Config.iMaxRecent <= 0) - return false; - - const std::string resolved = File::ResolvePath(gamePath.ToString()); - for (const auto &iso : g_Config.RecentIsos()) { - const std::string recent = File::ResolvePath(iso); - if (resolved == recent) - return true; - } - return false; -} - UI::EventReturn GameScreen::OnRemoveFromRecent(UI::EventParams &e) { - g_Config.RemoveRecent(gamePath_.ToString()); + g_recentFiles.Remove(gamePath_.ToString()); screenManager()->switchScreen(new MainScreen()); return UI::EVENT_DONE; } diff --git a/UI/GameScreen.h b/UI/GameScreen.h index 7c180ac074..d25b3e5254 100644 --- a/UI/GameScreen.h +++ b/UI/GameScreen.h @@ -47,7 +47,6 @@ protected: void CallbackDeleteConfig(bool yes); void CallbackDeleteSaveData(bool yes); void CallbackDeleteGame(bool yes); - bool isRecentGame(const Path &gamePath); private: UI::Choice *AddOtherChoice(UI::Choice *choice); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index c4a4f00ca3..7314e2d639 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -43,6 +43,7 @@ #include "Common/System/System.h" #include "Common/System/OSD.h" #include "Core/System.h" +#include "Core/Util/RecentFiles.h" #include "Core/Reporting.h" #include "Core/HLE/sceCtrl.h" #include "Core/ELF/PBPReader.h" @@ -685,7 +686,7 @@ bool GameBrowser::DisplayTopBar() { bool GameBrowser::HasSpecialFiles(std::vector &filenames) { if (path_.GetPath().ToString() == "!RECENT") { filenames.clear(); - for (auto &str : g_Config.RecentIsos()) { + for (auto &str : g_recentFiles.GetRecentFiles()) { filenames.emplace_back(str); } return true; @@ -1117,7 +1118,7 @@ void MainScreen::CreateViews() { System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE) == PERMISSION_STATUS_GRANTED; bool storageIsTemporary = IsTempPath(GetSysDirectory(DIRECTORY_SAVEDATA)) && !confirmedTemporary_; if (showRecent && !hasStorageAccess) { - showRecent = g_Config.HasRecentIsos(); + showRecent = g_recentFiles.HasAny(); } if (showRecent) { @@ -1198,7 +1199,7 @@ void MainScreen::CreateViews() { tabRemote->OnHighlight.Handle(this, &MainScreen::OnGameHighlight); } - if (g_Config.HasRecentIsos()) { + if (g_recentFiles.HasAny()) { tabHolder_->SetCurrentTab(std::clamp(g_Config.iDefaultTab, 0, g_Config.bRemoteTab ? 3 : 2), true); } else if (g_Config.iMaxRecent > 0) { tabHolder_->SetCurrentTab(1, true); @@ -1433,6 +1434,8 @@ void MainScreen::sendMessage(UIMessage message, const char *value) { } } else if (message == UIMessage::PERMISSION_GRANTED && !strcmp(value, "storage")) { RecreateViews(); + } else if (message == UIMessage::RECENT_FILES_CHANGED) { + RecreateViews(); } } @@ -1703,7 +1706,7 @@ void UmdReplaceScreen::CreateViews() { rightColumnItems->Add(new Spacer()); rightColumnItems->Add(new Choice(mm->T("Game Settings")))->OnClick.Handle(this, &UmdReplaceScreen::OnGameSettings); - if (g_Config.HasRecentIsos()) { + if (g_recentFiles.HasAny()) { leftColumn->SetCurrentTab(0, true); } else if (g_Config.iMaxRecent > 0) { leftColumn->SetCurrentTab(1, true); @@ -1774,7 +1777,7 @@ UI::EventReturn GridSettingsPopupScreen::GridMinusClick(UI::EventParams &e) { } UI::EventReturn GridSettingsPopupScreen::OnRecentClearClick(UI::EventParams &e) { - g_Config.ClearRecentIsos(); + g_recentFiles.Clear(); OnRecentChanged.Trigger(e); TriggerFinish(DR_OK); return UI::EVENT_DONE; diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 9e7dc20563..8129c51217 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -45,6 +45,7 @@ #include "Core/System.h" #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/HLE/sceUtility.h" +#include "Core/Util/RecentFiles.h" #include "GPU/GPUState.h" #include "GPU/GPUCommon.h" #include "GPU/Common/PostShader.h" @@ -211,7 +212,7 @@ public: lastIndex_ = nextIndex_; } - if (g_Config.HasRecentIsos()) { + if (g_recentFiles.HasAny()) { std::shared_ptr lastInfo = GetInfo(dc, lastIndex_); std::shared_ptr nextInfo = GetInfo(dc, nextIndex_); dc.Flush(); @@ -228,12 +229,14 @@ public: private: void CheckNext(UIContext &dc, double t) { - if (!g_Config.HasRecentIsos()) { + if (!g_recentFiles.HasAny()) { return; } + std::vector recents = g_recentFiles.GetRecentFiles(); + for (int index = lastIndex_ + 1; index != lastIndex_; ++index) { - if (index < 0 || index >= (int)g_Config.RecentIsos().size()) { + if (index < 0 || index >= (int)recents.size()) { if (lastIndex_ == -1) break; index = 0; @@ -258,9 +261,9 @@ private: if (index < 0) { return nullptr; } - const auto recentIsos = g_Config.RecentIsos(); + const auto recentIsos = g_recentFiles.GetRecentFiles(); if (index >= (int)recentIsos.size()) - return nullptr; + return std::shared_ptr(); return g_gameInfoCache->GetInfo(dc.GetDrawContext(), Path(recentIsos[index]), GameInfoFlags::BG); }