Qt: Implement gamelist caching

This commit is contained in:
spycrab 2017-08-14 16:05:46 +02:00
parent 935c1da357
commit b9c5a2af05
8 changed files with 257 additions and 55 deletions

View File

@ -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

View File

@ -188,6 +188,7 @@
<ClCompile Include="Config\PropertiesDialog.cpp" />
<ClCompile Include="Config\SettingsWindow.cpp" />
<ClCompile Include="GameList\GameFile.cpp" />
<ClCompile Include="GameList\GameFileCache.cpp" />
<ClCompile Include="GameList\GameList.cpp" />
<ClCompile Include="GameList\GameListModel.cpp" />
<ClCompile Include="GameList\GameTracker.cpp" />
@ -236,6 +237,7 @@
<ClInclude Include="Config\Mapping\WiimoteEmuExtension.h" />
<ClInclude Include="Config\Mapping\WiimoteEmuGeneral.h" />
<ClInclude Include="Config\Mapping\WiimoteEmuMotionControl.h" />
<ClInclude Include="GameList\GameFileCache.h" />
<ClInclude Include="QtUtils\BlockUserInputFilter.h" />
<ClInclude Include="QtUtils\ElidedButton.h" />
<ClInclude Include="QtUtils\ListTabWidget.h" />

View File

@ -2,8 +2,6 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <QCryptographicHash>
#include <QDataStream>
#include <QDir>
#include <QFileInfo>
#include <QImage>
@ -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<DiscIO::Language> GameFile::GetAvailableLanguages() const
{
return m_long_names.keys();
@ -43,6 +38,11 @@ ConvertLanguageMap(const std::map<DiscIO::Language, std::string>& 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<DiscIO::Volume> 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 <typename T, typename U = std::enable_if_t<std::is_enum<T>::value>>
QDataStream& operator<<(QDataStream& out, const T& enum_value)
{
out << static_cast<std::underlying_type_t<T>>(enum_value);
return out;
}
template <typename T, typename U = std::enable_if_t<std::is_enum<T>::value>>
QDataStream& operator>>(QDataStream& in, T& enum_value)
{
std::underlying_type_t<T> tmp;
in >> tmp;
enum_value = static_cast<T>(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<quint64>(integer);
return out;
}
QDataStream& operator>>(QDataStream& in, unsigned long& integer)
{
quint64 tmp;
in >> tmp;
integer = static_cast<unsigned long>(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;
}

View File

@ -5,6 +5,7 @@
#pragma once
#include <QDateTime>
#include <QFile>
#include <QMap>
#include <QPixmap>
#include <QString>
@ -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<DiscIO::Language, QString>& 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);

View File

@ -0,0 +1,87 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "GameFileCache.h"
#include <QDataStream>
#include <QDir>
#include <QFileInfo>
#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<std::mutex> guard(m_mutex);
return m_gamefiles.contains(path);
}
GameFile GameFileCache::GetFile(const QString& path)
{
std::lock_guard<std::mutex> guard(m_mutex);
return m_gamefiles[path];
}
void GameFileCache::Load()
{
std::lock_guard<std::mutex> 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<std::mutex> guard(m_mutex);
QFile file(m_path);
if (!file.open(QIODevice::WriteOnly))
return;
QDataStream stream(&file);
stream.setVersion(DATASTREAM_VERSION);
stream << static_cast<qint32>(CACHE_VERSION);
stream << m_gamefiles;
}
void GameFileCache::Update(const GameFile& gamefile)
{
std::lock_guard<std::mutex> guard(m_mutex);
m_gamefiles[gamefile.GetFilePath()] = gamefile;
}
QList<QString> GameFileCache::GetCached()
{
std::lock_guard<std::mutex> guard(m_mutex);
return m_gamefiles.keys();
}

View File

@ -0,0 +1,30 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <QFile>
#include <mutex>
#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<QString> GetCached();
private:
QString m_path;
QMap<QString, GameFile> m_gamefiles;
std::mutex m_mutex;
};

View File

@ -7,7 +7,9 @@
#include <QFile>
#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<GameFile>::create(cached_file));
return;
}
}
auto game = QSharedPointer<GameFile>::create(path);
if (game->IsValid())
{
emit GameLoaded(game);
cache.Update(*game);
cache.Save();
}
}
}

View File

@ -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<QString, QSet<QString>> m_tracked_files;
Common::WorkQueueThread<QString> m_load_thread;
GameFileCache cache;
};
Q_DECLARE_METATYPE(QSharedPointer<GameFile>)