summaryrefslogtreecommitdiff
path: root/Source/Core/UICommon/GameFileCache.cpp
diff options
context:
space:
mode:
authorLéo Lam <leolino.lam@gmail.com>2018-03-09 20:39:02 +0100
committerGitHub <noreply@github.com>2018-03-09 20:39:02 +0100
commit63838c013bde06ed13bfc1b5e24c53594934f641 (patch)
tree18fc96e96033eee8069b006d351c610eab9c8801 /Source/Core/UICommon/GameFileCache.cpp
parentef975b8c5664d28cf89c2cec9e89985a379b8c74 (diff)
parent1f1dae367d8aeb525df5558ce648d6f287983008 (diff)
Merge pull request #6281 from JosJuice/unify-gamelist-code
Unify ISOFile (wx) with GameFile (Qt) and put it in UICommon
Diffstat (limited to 'Source/Core/UICommon/GameFileCache.cpp')
-rw-r--r--Source/Core/UICommon/GameFileCache.cpp235
1 files changed, 235 insertions, 0 deletions
diff --git a/Source/Core/UICommon/GameFileCache.cpp b/Source/Core/UICommon/GameFileCache.cpp
new file mode 100644
index 0000000000..a0e1253c18
--- /dev/null
+++ b/Source/Core/UICommon/GameFileCache.cpp
@@ -0,0 +1,235 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "UICommon/GameFileCache.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "Common/ChunkFile.h"
+#include "Common/CommonTypes.h"
+#include "Common/File.h"
+#include "Common/FileSearch.h"
+#include "Common/FileUtil.h"
+
+#include "Core/TitleDatabase.h"
+
+#include "DiscIO/DirectoryBlob.h"
+
+#include "UICommon/GameFile.h"
+
+namespace UICommon
+{
+static constexpr u32 CACHE_REVISION = 7; // Last changed in PR 6281
+
+std::vector<std::string> FindAllGamePaths(const std::vector<std::string>& directories_to_scan,
+ bool recursive_scan)
+{
+ static const std::vector<std::string> search_extensions = {
+ ".gcm", ".tgc", ".iso", ".ciso", ".gcz", ".wbfs", ".wad", ".dol", ".elf"};
+
+ // TODO: We could process paths iteratively as they are found
+ return Common::DoFileSearch(directories_to_scan, search_extensions, recursive_scan);
+}
+
+void GameFileCache::ForEach(std::function<void(const std::shared_ptr<const GameFile>&)> f) const
+{
+ for (const std::shared_ptr<const GameFile>& item : m_cached_files)
+ f(item);
+}
+
+void GameFileCache::Clear()
+{
+ m_cached_files.clear();
+}
+
+std::shared_ptr<const GameFile> GameFileCache::AddOrGet(const std::string& path,
+ bool* cache_changed,
+ const Core::TitleDatabase& title_database)
+{
+ auto it = std::find_if(
+ m_cached_files.begin(), m_cached_files.end(),
+ [&path](const std::shared_ptr<GameFile>& file) { return file->GetFilePath() == path; });
+ const bool found = it != m_cached_files.cend();
+ if (!found)
+ m_cached_files.emplace_back(std::make_shared<GameFile>(path));
+ std::shared_ptr<GameFile>& result = found ? *it : m_cached_files.back();
+ if (UpdateAdditionalMetadata(&result, title_database) || !found)
+ *cache_changed = true;
+
+ return result;
+}
+
+bool GameFileCache::Update(const std::vector<std::string>& all_game_paths)
+{
+ // Copy game paths into a set, except ones that match DiscIO::ShouldHideFromGameList.
+ // TODO: Prevent DoFileSearch from looking inside /files/ directories of DirectoryBlobs at all?
+ // TODO: Make DoFileSearch support filter predicates so we don't have remove things afterwards?
+ std::unordered_set<std::string> game_paths;
+ game_paths.reserve(all_game_paths.size());
+ for (const std::string& path : all_game_paths)
+ {
+ if (!DiscIO::ShouldHideFromGameList(path))
+ game_paths.insert(path);
+ }
+
+ bool cache_changed = false;
+
+ // Delete paths that aren't in game_paths from m_cached_files,
+ // while simultaneously deleting paths that aren't in m_cached_files from game_paths.
+ // For the sake of speed, we don't care about maintaining the order of m_cached_files.
+ {
+ auto it = m_cached_files.begin();
+ auto end = m_cached_files.end();
+ while (it != end)
+ {
+ if (game_paths.erase((*it)->GetFilePath()))
+ {
+ ++it;
+ }
+ else
+ {
+ cache_changed = true;
+ --end;
+ *it = std::move(*end);
+ }
+ }
+ m_cached_files.erase(it, m_cached_files.end());
+ }
+
+ // Now that the previous loop has run, game_paths only contains paths that
+ // aren't in m_cached_files, so we simply add all of them to m_cached_files.
+ for (const auto& path : game_paths)
+ {
+ auto file = std::make_shared<GameFile>(path);
+ if (file->IsValid())
+ {
+ cache_changed = true;
+ m_cached_files.push_back(std::move(file));
+ }
+ }
+
+ return cache_changed;
+}
+
+bool GameFileCache::UpdateAdditionalMetadata(const Core::TitleDatabase& title_database)
+{
+ bool cache_changed = false;
+
+ for (auto& file : m_cached_files)
+ cache_changed |= UpdateAdditionalMetadata(&file, title_database);
+
+ return cache_changed;
+}
+
+bool GameFileCache::UpdateAdditionalMetadata(std::shared_ptr<GameFile>* game_file,
+ const Core::TitleDatabase& title_database)
+{
+ const bool emu_state_changed = (*game_file)->EmuStateChanged();
+ const bool banner_changed = (*game_file)->BannerChanged();
+ const bool custom_title_changed = (*game_file)->CustomNameChanged(title_database);
+ if (!emu_state_changed && !banner_changed && !custom_title_changed)
+ return false;
+
+ // If a cached file needs an update, apply the updates to a copy and delete the original.
+ // This makes the usage of cached files in other threads safe.
+
+ std::shared_ptr<GameFile> copy = std::make_shared<GameFile>(**game_file);
+ if (emu_state_changed)
+ copy->EmuStateCommit();
+ if (banner_changed)
+ copy->BannerCommit();
+ if (custom_title_changed)
+ copy->CustomNameCommit();
+ *game_file = std::move(copy);
+
+ return true;
+}
+
+bool GameFileCache::Load()
+{
+ return SyncCacheFile(false);
+}
+
+bool GameFileCache::Save()
+{
+ return SyncCacheFile(true);
+}
+
+bool GameFileCache::SyncCacheFile(bool save)
+{
+ std::string filename(File::GetUserPath(D_CACHE_IDX) + "gamelist.cache");
+ const char* open_mode = save ? "wb" : "rb";
+ File::IOFile f(filename, open_mode);
+ if (!f)
+ return false;
+ bool success = false;
+ if (save)
+ {
+ // Measure the size of the buffer.
+ u8* ptr = nullptr;
+ PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
+ DoState(&p);
+ const size_t buffer_size = reinterpret_cast<size_t>(ptr);
+
+ // Then actually do the write.
+ std::vector<u8> buffer(buffer_size);
+ ptr = &buffer[0];
+ p.SetMode(PointerWrap::MODE_WRITE);
+ DoState(&p, buffer_size);
+ if (f.WriteBytes(buffer.data(), buffer.size()))
+ success = true;
+ }
+ else
+ {
+ std::vector<u8> buffer(f.GetSize());
+ if (buffer.size() && f.ReadBytes(buffer.data(), buffer.size()))
+ {
+ u8* ptr = buffer.data();
+ PointerWrap p(&ptr, PointerWrap::MODE_READ);
+ DoState(&p, buffer.size());
+ if (p.GetMode() == PointerWrap::MODE_READ)
+ success = true;
+ }
+ }
+ if (!success)
+ {
+ // If some file operation failed, try to delete the probably-corrupted cache
+ f.Close();
+ File::Delete(filename);
+ }
+ return success;
+}
+
+void GameFileCache::DoState(PointerWrap* p, u64 size)
+{
+ struct
+ {
+ u32 revision;
+ u64 expected_size;
+ } header = {CACHE_REVISION, size};
+ p->Do(header);
+ if (p->GetMode() == PointerWrap::MODE_READ)
+ {
+ if (header.revision != CACHE_REVISION || header.expected_size != size)
+ {
+ p->SetMode(PointerWrap::MODE_MEASURE);
+ return;
+ }
+ }
+ p->DoEachElement(m_cached_files, [](PointerWrap& state, std::shared_ptr<GameFile>& elem) {
+ if (state.GetMode() == PointerWrap::MODE_READ)
+ elem = std::make_shared<GameFile>();
+ elem->DoState(state);
+ });
+}
+
+} // namespace DiscIO