From b9c5a2af052425ecf0ec41db86c2b01c2f0bafbd Mon Sep 17 00:00:00 2001 From: spycrab Date: Mon, 14 Aug 2017 16:05:46 +0200 Subject: [PATCH] Qt: Implement gamelist caching --- Source/Core/DolphinQt2/CMakeLists.txt | 1 + Source/Core/DolphinQt2/DolphinQt2.vcxproj | 2 + Source/Core/DolphinQt2/GameList/GameFile.cpp | 155 ++++++++++++------ Source/Core/DolphinQt2/GameList/GameFile.h | 16 +- .../DolphinQt2/GameList/GameFileCache.cpp | 87 ++++++++++ .../Core/DolphinQt2/GameList/GameFileCache.h | 30 ++++ .../Core/DolphinQt2/GameList/GameTracker.cpp | 19 +++ Source/Core/DolphinQt2/GameList/GameTracker.h | 2 + 8 files changed, 257 insertions(+), 55 deletions(-) create mode 100644 Source/Core/DolphinQt2/GameList/GameFileCache.cpp create mode 100644 Source/Core/DolphinQt2/GameList/GameFileCache.h diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index 04726b8434..6b42dea3bc 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -59,6 +59,7 @@ set(SRCS Config/Mapping/WiimoteEmuMotionControl.cpp Config/PropertiesDialog.cpp Config/SettingsWindow.cpp + GameList/GameFileCache.cpp GameList/GameFile.cpp GameList/GameList.cpp GameList/GameListModel.cpp diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index b9c97c4ccc..d160584d4f 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -188,6 +188,7 @@ + @@ -236,6 +237,7 @@ + diff --git a/Source/Core/DolphinQt2/GameList/GameFile.cpp b/Source/Core/DolphinQt2/GameList/GameFile.cpp index c22b5e0ecf..035028c9f9 100644 --- a/Source/Core/DolphinQt2/GameList/GameFile.cpp +++ b/Source/Core/DolphinQt2/GameList/GameFile.cpp @@ -2,8 +2,6 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include -#include #include #include #include @@ -26,9 +24,6 @@ #include "DolphinQt2/Resources.h" #include "DolphinQt2/Settings.h" -static const int CACHE_VERSION = 13; // Last changed in PR #3261 -static const int DATASTREAM_VERSION = QDataStream::Qt_5_5; - QList GameFile::GetAvailableLanguages() const { return m_long_names.keys(); @@ -43,6 +38,11 @@ ConvertLanguageMap(const std::map& map) return result; } +GameFile::GameFile() +{ + m_valid = false; +} + GameFile::GameFile(const QString& path) : m_path(path) { m_valid = false; @@ -50,16 +50,13 @@ GameFile::GameFile(const QString& path) : m_path(path) if (!LoadFileInfo(path)) return; - if (!TryLoadCache()) + if (TryLoadVolume()) { - if (TryLoadVolume()) - { - LoadState(); - } - else if (!TryLoadElfDol()) - { - return; - } + LoadState(); + } + else if (!TryLoadElfDol()) + { + return; } m_valid = true; @@ -76,16 +73,6 @@ bool GameFile::IsValid() const return true; } -QString GameFile::GetCacheFileName() const -{ - QString folder = QString::fromStdString(File::GetUserPath(D_CACHE_IDX)); - // Append a hash of the full path to prevent name clashes between - // files with the same names in different folders. - QString hash = - QString::fromUtf8(QCryptographicHash::hash(m_path.toUtf8(), QCryptographicHash::Md5).toHex()); - return folder + GetFileName() + hash; -} - void GameFile::ReadBanner(const DiscIO::Volume& volume) { int width, height; @@ -129,27 +116,6 @@ bool GameFile::IsElfOrDol() return extension == QStringLiteral("elf") || extension == QStringLiteral("dol"); } -bool GameFile::TryLoadCache() -{ - QFile cache(GetCacheFileName()); - if (!cache.exists()) - return false; - if (!cache.open(QIODevice::ReadOnly)) - return false; - if (QFileInfo(cache).lastModified() < m_last_modified) - return false; - - QDataStream in(&cache); - in.setVersion(DATASTREAM_VERSION); - - int cache_version; - in >> cache_version; - if (cache_version != CACHE_VERSION) - return false; - - return false; -} - bool GameFile::TryLoadVolume() { QSharedPointer volume( @@ -179,7 +145,6 @@ bool GameFile::TryLoadVolume() ReadBanner(*volume); - SaveCache(); return true; } @@ -199,11 +164,6 @@ bool GameFile::TryLoadElfDol() return true; } -void GameFile::SaveCache() -{ - // TODO -} - QString GameFile::GetFileName() const { return QFileInfo(m_path).fileName(); @@ -427,3 +387,96 @@ QString FormatSize(qint64 size) } return QStringLiteral("%1 %2").arg(QString::number(num, 'f', 1)).arg(unit); } + +template ::value>> +QDataStream& operator<<(QDataStream& out, const T& enum_value) +{ + out << static_cast>(enum_value); + return out; +} + +template ::value>> +QDataStream& operator>>(QDataStream& in, T& enum_value) +{ + std::underlying_type_t tmp; + in >> tmp; + enum_value = static_cast(tmp); + return in; +} + +// Some C++ implementations define uint64_t as an 'unsigned long', but QDataStream only has built-in +// overloads for quint64, which is an 'unsigned long long' on Unix +QDataStream& operator<<(QDataStream& out, const unsigned long& integer) +{ + out << static_cast(integer); + return out; +} +QDataStream& operator>>(QDataStream& in, unsigned long& integer) +{ + quint64 tmp; + in >> tmp; + integer = static_cast(tmp); + return in; +} + +QDataStream& operator<<(QDataStream& out, const GameFile& file) +{ + out << file.m_last_modified; + out << file.m_path; + out << file.m_title_id; + out << file.m_game_id; + out << file.m_maker_id; + out << file.m_maker; + out << file.m_long_makers; + out << file.m_short_makers; + out << file.m_internal_name; + out << file.m_long_names; + out << file.m_short_names; + out << file.m_platform; + out << file.m_region; + out << file.m_country; + out << file.m_blob_type; + out << file.m_size; + out << file.m_raw_size; + out << file.m_descriptions; + out << file.m_revision; + out << file.m_disc_number; + out << file.m_issues; + out << file.m_rating; + out << file.m_apploader_date; + out << file.m_banner; + + return out; +} + +QDataStream& operator>>(QDataStream& in, GameFile& file) +{ + in >> file.m_last_modified; + in >> file.m_path; + in >> file.m_title_id; + in >> file.m_game_id; + in >> file.m_maker_id; + in >> file.m_maker; + in >> file.m_long_makers; + in >> file.m_short_makers; + in >> file.m_internal_name; + in >> file.m_long_names; + in >> file.m_short_names; + in >> file.m_platform; + in >> file.m_region; + in >> file.m_country; + in >> file.m_blob_type; + in >> file.m_size; + in >> file.m_raw_size; + in >> file.m_descriptions; + in >> file.m_revision; + in >> file.m_disc_number; + in >> file.m_issues; + in >> file.m_rating; + in >> file.m_apploader_date; + in >> file.m_banner; + + file.m_valid = true; + + return in; +} diff --git a/Source/Core/DolphinQt2/GameList/GameFile.h b/Source/Core/DolphinQt2/GameList/GameFile.h index cc36d52f64..c5791ed7cc 100644 --- a/Source/Core/DolphinQt2/GameList/GameFile.h +++ b/Source/Core/DolphinQt2/GameList/GameFile.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -21,10 +22,10 @@ enum class Platform; class Volume; } -// TODO cache class GameFile final { public: + GameFile(); explicit GameFile(const QString& path); bool IsValid() const; @@ -34,6 +35,7 @@ public: QString GetFileExtension() const; QString GetFileFolder() const; qint64 GetFileSize() const { return m_size; } + QDateTime GetLastModified() const { return m_last_modified; } // The rest will not. QString GetGameID() const { return m_game_id; } QString GetMakerID() const { return m_maker_id; } @@ -73,18 +75,18 @@ public: bool Uninstall(); bool ExportWiiSave(); + friend QDataStream& operator<<(QDataStream& out, const GameFile& file); + friend QDataStream& operator>>(QDataStream& in, GameFile& file); + private: QString GetBannerString(const QMap& m) const; - QString GetCacheFileName() const; void ReadBanner(const DiscIO::Volume& volume); bool LoadFileInfo(const QString& path); void LoadState(); bool IsElfOrDol(); bool TryLoadElfDol(); - bool TryLoadCache(); bool TryLoadVolume(); - void SaveCache(); bool m_valid; QString m_path; @@ -115,3 +117,9 @@ private: }; QString FormatSize(qint64 size); + +QDataStream& operator<<(QDataStream& out, const GameFile& file); +QDataStream& operator>>(QDataStream& in, GameFile& file); + +QDataStream& operator<<(QDataStream& out, const unsigned long& file); +QDataStream& operator>>(QDataStream& in, unsigned long& file); diff --git a/Source/Core/DolphinQt2/GameList/GameFileCache.cpp b/Source/Core/DolphinQt2/GameList/GameFileCache.cpp new file mode 100644 index 0000000000..40e1928649 --- /dev/null +++ b/Source/Core/DolphinQt2/GameList/GameFileCache.cpp @@ -0,0 +1,87 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "GameFileCache.h" + +#include +#include +#include + +#include "Common/FileUtil.h" +#include "Core/ConfigManager.h" +#include "DolphinQt2/Settings.h" + +static const int CACHE_VERSION = 1; // Last changed in PR #5927 +static const int DATASTREAM_VERSION = QDataStream::Qt_5_0; + +GameFileCache::GameFileCache() + : m_path(QString::fromStdString(File::GetUserPath(D_CACHE_IDX) + "qt_gamefile.cache")) +{ +} + +bool GameFileCache::IsCached(const QString& path) +{ + std::lock_guard guard(m_mutex); + + return m_gamefiles.contains(path); +} + +GameFile GameFileCache::GetFile(const QString& path) +{ + std::lock_guard guard(m_mutex); + + return m_gamefiles[path]; +} + +void GameFileCache::Load() +{ + std::lock_guard guard(m_mutex); + + QFile file(m_path); + + if (!file.open(QIODevice::ReadOnly)) + return; + + QDataStream stream(&file); + stream.setVersion(DATASTREAM_VERSION); + + qint32 cache_version; + stream >> cache_version; + + // If the cache file is using an older version, ignore it and create it from scratch + if (cache_version != CACHE_VERSION) + return; + + stream >> m_gamefiles; +} + +void GameFileCache::Save() +{ + std::lock_guard guard(m_mutex); + + QFile file(m_path); + + if (!file.open(QIODevice::WriteOnly)) + return; + + QDataStream stream(&file); + stream.setVersion(DATASTREAM_VERSION); + + stream << static_cast(CACHE_VERSION); + stream << m_gamefiles; +} + +void GameFileCache::Update(const GameFile& gamefile) +{ + std::lock_guard guard(m_mutex); + + m_gamefiles[gamefile.GetFilePath()] = gamefile; +} + +QList GameFileCache::GetCached() +{ + std::lock_guard guard(m_mutex); + + return m_gamefiles.keys(); +} diff --git a/Source/Core/DolphinQt2/GameList/GameFileCache.h b/Source/Core/DolphinQt2/GameList/GameFileCache.h new file mode 100644 index 0000000000..b1a505c0f2 --- /dev/null +++ b/Source/Core/DolphinQt2/GameList/GameFileCache.h @@ -0,0 +1,30 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "DolphinQt2/GameList/GameFile.h" + +class GameFileCache +{ +public: + explicit GameFileCache(); + + void Update(const GameFile& gamefile); + void Save(); + void Load(); + bool IsCached(const QString& path); + GameFile GetFile(const QString& path); + QList GetCached(); + +private: + QString m_path; + + QMap m_gamefiles; + std::mutex m_mutex; +}; diff --git a/Source/Core/DolphinQt2/GameList/GameTracker.cpp b/Source/Core/DolphinQt2/GameList/GameTracker.cpp index f1558f153d..85539f4d33 100644 --- a/Source/Core/DolphinQt2/GameList/GameTracker.cpp +++ b/Source/Core/DolphinQt2/GameList/GameTracker.cpp @@ -7,7 +7,9 @@ #include #include "DiscIO/DirectoryBlob.h" +#include "DolphinQt2/GameList/GameFileCache.h" #include "DolphinQt2/GameList/GameTracker.h" +#include "DolphinQt2/QtUtils/QueueOnObject.h" #include "DolphinQt2/Settings.h" static const QStringList game_filters{ @@ -21,6 +23,8 @@ GameTracker::GameTracker(QObject* parent) : QFileSystemWatcher(parent) connect(this, &QFileSystemWatcher::directoryChanged, this, &GameTracker::UpdateDirectory); connect(this, &QFileSystemWatcher::fileChanged, this, &GameTracker::UpdateFile); + cache.Load(); + m_load_thread.Reset([this](const QString& path) { LoadGame(path); }); } @@ -128,8 +132,23 @@ void GameTracker::LoadGame(const QString& path) { if (!DiscIO::ShouldHideFromGameList(path.toStdString())) { + if (cache.IsCached(path)) + { + const QDateTime last_modified = QFileInfo(path).lastModified(); + auto cached_file = cache.GetFile(path); + if (cached_file.GetLastModified() >= last_modified) + { + emit GameLoaded(QSharedPointer::create(cached_file)); + return; + } + } + auto game = QSharedPointer::create(path); if (game->IsValid()) + { emit GameLoaded(game); + cache.Update(*game); + cache.Save(); + } } } diff --git a/Source/Core/DolphinQt2/GameList/GameTracker.h b/Source/Core/DolphinQt2/GameList/GameTracker.h index 550a6548ee..b063b7dcbe 100644 --- a/Source/Core/DolphinQt2/GameList/GameTracker.h +++ b/Source/Core/DolphinQt2/GameList/GameTracker.h @@ -12,6 +12,7 @@ #include "Common/WorkQueueThread.h" #include "DolphinQt2/GameList/GameFile.h" +#include "DolphinQt2/GameList/GameFileCache.h" // Watches directories and loads GameFiles in a separate thread. // To use this, just add directories using AddDirectory, and listen for the @@ -39,6 +40,7 @@ private: // game path -> directories that track it QMap> m_tracked_files; Common::WorkQueueThread m_load_thread; + GameFileCache cache; }; Q_DECLARE_METATYPE(QSharedPointer)