Browse Source
Read actual games from game dirs
Read actual games from game dirs
TODO: GameList/GameListModel can be abstracted in some way GameListWorker should work on this abstraction. I also need to add grouped directories just like Widgets Signed-off-by: crueter <crueter@eden-emu.dev>pull/3016/head
No known key found for this signature in database
GPG Key ID: 425ACD2D4830EBC6
13 changed files with 811 additions and 94 deletions
-
17src/Eden/Interface/CMakeLists.txt
-
5src/Eden/Main/GameCarouselCard.qml
-
6src/Eden/Main/GameGridCard.qml
-
32src/Eden/Main/GameList.qml
-
5src/Eden/Models/CMakeLists.txt
-
45src/Eden/Models/GameIconProvider.cpp
-
16src/Eden/Models/GameIconProvider.h
-
143src/Eden/Models/GameListModel.cpp
-
26src/Eden/Models/GameListModel.h
-
512src/Eden/Models/GameListWorker.cpp
-
81src/Eden/Models/GameListWorker.h
-
15src/Eden/Native/main.cpp
-
2src/video_core/CMakeLists.txt
@ -0,0 +1,45 @@ |
|||
#include <qnamespace.h>
|
|||
#include "GameIconProvider.h"
|
|||
#include "qt_common/uisettings.h"
|
|||
|
|||
/**
|
|||
* Gets the default icon (for games without valid title metadata) |
|||
* @param size The desired width and height of the default icon. |
|||
* @return QPixmap default icon |
|||
*/ |
|||
static QPixmap GetDefaultIcon(const QSize &size) |
|||
{ |
|||
QPixmap icon(size.width(), size.height()); |
|||
icon.fill(Qt::transparent); |
|||
return icon; |
|||
} |
|||
|
|||
GameIconProvider::GameIconProvider() |
|||
: QQuickImageProvider(QQuickImageProvider::Pixmap) |
|||
{} |
|||
|
|||
QPixmap GameIconProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) |
|||
{ |
|||
const u32 default_size = UISettings::values.game_icon_size.GetValue(); |
|||
QSize trueSize = QSize(default_size, default_size); |
|||
if (requestedSize.isValid()) { |
|||
trueSize = requestedSize; |
|||
} |
|||
|
|||
QPixmap pixmap = m_pixmaps.value(id, GetDefaultIcon(trueSize)); |
|||
|
|||
if (size) |
|||
*size = QSize(trueSize.width(), trueSize.height()); |
|||
|
|||
return pixmap; |
|||
} |
|||
|
|||
void GameIconProvider::addPixmap(const QString &key, const QPixmap pixmap) |
|||
{ |
|||
m_pixmaps.insert(key, pixmap); |
|||
} |
|||
|
|||
void GameIconProvider::clear() |
|||
{ |
|||
m_pixmaps.clear(); |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
#pragma once |
|||
|
|||
#include <QQuickImageProvider> |
|||
|
|||
class GameIconProvider : public QQuickImageProvider |
|||
{ |
|||
public: |
|||
GameIconProvider(); |
|||
|
|||
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; |
|||
|
|||
void addPixmap(const QString &key, const QPixmap pixmap); |
|||
void clear(); |
|||
private: |
|||
QMap<QString, QPixmap> m_pixmaps; |
|||
}; |
|||
@ -0,0 +1,512 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
|||
#include <memory>
|
|||
#include <string>
|
|||
#include <utility>
|
|||
#include <vector>
|
|||
|
|||
#include "GameListModel.h"
|
|||
#include "common/logging/types.h"
|
|||
|
|||
#include <QDir>
|
|||
#include <QFile>
|
|||
#include <QFileInfo>
|
|||
#include <QSettings>
|
|||
#include <QStandardItem>
|
|||
|
|||
#include "GameListWorker.h"
|
|||
#include "common/fs/fs.h"
|
|||
#include "common/fs/path_util.h"
|
|||
#include "core/core.h"
|
|||
#include "core/file_sys/card_image.h"
|
|||
#include "core/file_sys/content_archive.h"
|
|||
#include "core/file_sys/control_metadata.h"
|
|||
#include "core/file_sys/fs_filesystem.h"
|
|||
#include "core/file_sys/nca_metadata.h"
|
|||
#include "core/file_sys/patch_manager.h"
|
|||
#include "core/file_sys/registered_cache.h"
|
|||
#include "core/file_sys/submission_package.h"
|
|||
#include "core/loader/loader.h"
|
|||
#include "qt_common/qt_common.h"
|
|||
#include "qt_common/uisettings.h"
|
|||
|
|||
namespace { |
|||
|
|||
/**
|
|||
* Gets the default icon (for games without valid title metadata) |
|||
* @param size The desired width and height of the default icon. |
|||
* @return QPixmap default icon |
|||
*/ |
|||
static QPixmap GetDefaultIcon(u32 size) { |
|||
QPixmap icon(size, size); |
|||
icon.fill(Qt::transparent); |
|||
return icon; |
|||
} |
|||
|
|||
QString GetGameListCachedObject(const std::string& filename, |
|||
const std::string& ext, |
|||
const std::function<QString()>& generator) |
|||
{ |
|||
if (!UISettings::values.cache_game_list || filename == "0000000000000000") { |
|||
return generator(); |
|||
} |
|||
|
|||
const auto path = Common::FS::PathToUTF8String( |
|||
Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "game_list" |
|||
/ fmt::format("{}.{}", filename, ext)); |
|||
|
|||
void(Common::FS::CreateParentDirs(path)); |
|||
|
|||
if (!Common::FS::Exists(path)) { |
|||
const auto str = generator(); |
|||
|
|||
QFile file{QString::fromStdString(path)}; |
|||
if (file.open(QFile::WriteOnly)) { |
|||
file.write(str.toUtf8()); |
|||
} |
|||
|
|||
return str; |
|||
} |
|||
|
|||
QFile file{QString::fromStdString(path)}; |
|||
if (file.open(QFile::ReadOnly)) { |
|||
return QString::fromUtf8(file.readAll()); |
|||
} |
|||
|
|||
return generator(); |
|||
} |
|||
|
|||
std::pair<std::vector<u8>, std::string> GetGameListCachedObject( |
|||
const std::string& filename, |
|||
const std::string& ext, |
|||
const std::function<std::pair<std::vector<u8>, std::string>()>& generator) |
|||
{ |
|||
if (!UISettings::values.cache_game_list || filename == "0000000000000000") { |
|||
return generator(); |
|||
} |
|||
|
|||
const auto game_list_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) |
|||
/ "game_list"; |
|||
const auto jpeg_name = fmt::format("{}.jpeg", filename); |
|||
const auto app_name = fmt::format("{}.appname.txt", filename); |
|||
|
|||
const auto path1 = Common::FS::PathToUTF8String(game_list_dir / jpeg_name); |
|||
const auto path2 = Common::FS::PathToUTF8String(game_list_dir / app_name); |
|||
|
|||
void(Common::FS::CreateParentDirs(path1)); |
|||
|
|||
if (!Common::FS::Exists(path1) || !Common::FS::Exists(path2)) { |
|||
const auto [icon, nacp] = generator(); |
|||
|
|||
QFile file1{QString::fromStdString(path1)}; |
|||
if (!file1.open(QFile::WriteOnly)) { |
|||
LOG_ERROR(Frontend, "Failed to open cache file."); |
|||
return generator(); |
|||
} |
|||
|
|||
if (!file1.resize(icon.size())) { |
|||
LOG_ERROR(Frontend, "Failed to resize cache file to necessary size."); |
|||
return generator(); |
|||
} |
|||
|
|||
if (file1.write(reinterpret_cast<const char*>(icon.data()), icon.size()) |
|||
!= s64(icon.size())) { |
|||
LOG_ERROR(Frontend, "Failed to write data to cache file."); |
|||
return generator(); |
|||
} |
|||
|
|||
QFile file2{QString::fromStdString(path2)}; |
|||
if (file2.open(QFile::WriteOnly)) { |
|||
file2.write(nacp.data(), nacp.size()); |
|||
} |
|||
|
|||
return std::make_pair(icon, nacp); |
|||
} |
|||
|
|||
QFile file1(QString::fromStdString(path1)); |
|||
QFile file2(QString::fromStdString(path2)); |
|||
|
|||
if (!file1.open(QFile::ReadOnly)) { |
|||
LOG_ERROR(Frontend, "Failed to open cache file for reading."); |
|||
return generator(); |
|||
} |
|||
|
|||
if (!file2.open(QFile::ReadOnly)) { |
|||
LOG_ERROR(Frontend, "Failed to open cache file for reading."); |
|||
return generator(); |
|||
} |
|||
|
|||
std::vector<u8> vec(file1.size()); |
|||
if (file1.read(reinterpret_cast<char*>(vec.data()), vec.size()) |
|||
!= static_cast<s64>(vec.size())) { |
|||
return generator(); |
|||
} |
|||
|
|||
const auto data = file2.readAll(); |
|||
return std::make_pair(vec, data.toStdString()); |
|||
} |
|||
|
|||
void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, |
|||
const FileSys::NCA& nca, |
|||
std::vector<u8>& icon, |
|||
std::string& name) |
|||
{ |
|||
std::tie(icon, name) = GetGameListCachedObject( |
|||
fmt::format("{:016X}", patch_manager.GetTitleID()), {}, [&patch_manager, &nca] { |
|||
const auto [nacp, icon_f] = patch_manager.ParseControlNCA(nca); |
|||
return std::make_pair(icon_f->ReadAllBytes(), nacp->GetApplicationName()); |
|||
}); |
|||
} |
|||
|
|||
bool HasSupportedFileExtension(const std::string& file_name) |
|||
{ |
|||
const QFileInfo file = QFileInfo(QString::fromStdString(file_name)); |
|||
return QtCommon::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); |
|||
} |
|||
|
|||
bool IsExtractedNCAMain(const std::string& file_name) |
|||
{ |
|||
return QFileInfo(QString::fromStdString(file_name)).fileName() == QStringLiteral("main"); |
|||
} |
|||
|
|||
// QString FormatGameName(const std::string& physical_name)
|
|||
// {
|
|||
// const QString physical_name_as_qstring = QString::fromStdString(physical_name);
|
|||
// const QFileInfo file_info(physical_name_as_qstring);
|
|||
|
|||
// if (IsExtractedNCAMain(physical_name)) {
|
|||
// return file_info.dir().path();
|
|||
// }
|
|||
|
|||
// return physical_name_as_qstring;
|
|||
// }
|
|||
|
|||
QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, |
|||
Loader::AppLoader& loader, |
|||
bool updatable = true) |
|||
{ |
|||
QString out; |
|||
FileSys::VirtualFile update_raw; |
|||
loader.ReadUpdateRaw(update_raw); |
|||
for (const auto& patch : patch_manager.GetPatches(update_raw)) { |
|||
const bool is_update = patch.name == "Update"; |
|||
if (!updatable && is_update) { |
|||
continue; |
|||
} |
|||
|
|||
const QString type = QString::fromStdString(patch.enabled ? patch.name |
|||
: "[D] " + patch.name); |
|||
|
|||
if (patch.version.empty()) { |
|||
out.append(QStringLiteral("%1\n").arg(type)); |
|||
} else { |
|||
auto ver = patch.version; |
|||
|
|||
// Display container name for packed updates
|
|||
if (is_update && ver == "PACKED") { |
|||
ver = Loader::GetFileTypeString(loader.GetFileType()); |
|||
} |
|||
|
|||
out.append(QStringLiteral("%1 (%2)\n").arg(type, QString::fromStdString(ver))); |
|||
} |
|||
} |
|||
|
|||
out.chop(1); |
|||
return out; |
|||
} |
|||
|
|||
QStandardItem* MakeGameListEntry(const std::string& path, |
|||
const std::string& name, |
|||
const std::size_t size, |
|||
const std::vector<u8>& icon, |
|||
Loader::AppLoader& loader, |
|||
u64 program_id, |
|||
const FileSys::PatchManager& patch) |
|||
{ |
|||
auto const file_type = loader.GetFileType(); |
|||
auto const file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type)); |
|||
|
|||
QString patch_versions = GetGameListCachedObject( |
|||
fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { |
|||
return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); |
|||
}); |
|||
|
|||
QStandardItem* item = new QStandardItem(QString::fromStdString(name)); |
|||
enum GLMRoleTypes { NAME = Qt::UserRole + 1, PATH, FILESIZE }; |
|||
|
|||
item->setData(QString::fromStdString(name), GameListModel::NAME); |
|||
item->setData(QString::fromStdString(path), GameListModel::PATH); |
|||
|
|||
const u32 pic_size = UISettings::values.game_icon_size.GetValue(); |
|||
|
|||
QPixmap picture; |
|||
if (!picture.loadFromData(icon.data(), static_cast<u32>(icon.size()))) { |
|||
picture = GetDefaultIcon(pic_size); |
|||
} |
|||
picture = picture.scaled(pic_size, pic_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); |
|||
|
|||
item->setData(picture, GameListModel::ICON); |
|||
// item->setData(QVariant::fromValue(size), GameListModel::FILESIZE);
|
|||
|
|||
return item; |
|||
} |
|||
|
|||
} // Anonymous namespace
|
|||
|
|||
GameListWorker::GameListWorker(QVector<UISettings::GameDir>& game_dirs_) |
|||
: game_dirs{game_dirs_} |
|||
{ |
|||
// We want the game list to manage our lifetime.
|
|||
setAutoDelete(false); |
|||
} |
|||
|
|||
GameListWorker::~GameListWorker() |
|||
{ |
|||
this->disconnect(); |
|||
stop_requested.store(true); |
|||
processing_completed.Wait(); |
|||
} |
|||
|
|||
void GameListWorker::ProcessEvents(GameListModel* game_list) |
|||
{ |
|||
while (true) { |
|||
std::function<void(GameListModel*)> func; |
|||
{ |
|||
// Lock queue to protect concurrent modification.
|
|||
std::scoped_lock lk(lock); |
|||
|
|||
// If we can't pop a function, return.
|
|||
if (queued_events.empty()) { |
|||
return; |
|||
} |
|||
|
|||
// Pop a function.
|
|||
func = std::move(queued_events.back()); |
|||
queued_events.pop_back(); |
|||
} |
|||
|
|||
// Run the function.
|
|||
func(game_list); |
|||
} |
|||
} |
|||
|
|||
template<typename F> |
|||
void GameListWorker::RecordEvent(F&& func) |
|||
{ |
|||
{ |
|||
// Lock queue to protect concurrent modification.
|
|||
std::scoped_lock lk(lock); |
|||
|
|||
// Add the function into the front of the queue.
|
|||
queued_events.emplace_front(std::move(func)); |
|||
} |
|||
|
|||
// Data now available.
|
|||
emit DataAvailable(); |
|||
} |
|||
|
|||
void GameListWorker::AddTitlesToGameList(UISettings::GameDir& parent_dir) |
|||
{ |
|||
using namespace FileSys; |
|||
|
|||
const auto& cache = QtCommon::system->GetContentProviderUnion(); |
|||
|
|||
auto installed_games = cache.ListEntriesFilterOrigin(std::nullopt, |
|||
TitleType::Application, |
|||
ContentRecordType::Program); |
|||
|
|||
for (const auto& [slot, game] : installed_games) { |
|||
if (slot == ContentProviderUnionSlot::FrontendManual) { |
|||
continue; |
|||
} |
|||
|
|||
const auto file = cache.GetEntryUnparsed(game.title_id, game.type); |
|||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(*QtCommon::system, file); |
|||
if (!loader) { |
|||
continue; |
|||
} |
|||
|
|||
std::vector<u8> icon; |
|||
std::string name; |
|||
u64 program_id = 0; |
|||
const auto result = loader->ReadProgramId(program_id); |
|||
|
|||
if (result != Loader::ResultStatus::Success) { |
|||
continue; |
|||
} |
|||
|
|||
const PatchManager patch{program_id, |
|||
QtCommon::system->GetFileSystemController(), |
|||
QtCommon::system->GetContentProvider()}; |
|||
LOG_INFO(Frontend, "PatchManager initiated for id {:X}", program_id); |
|||
const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control); |
|||
if (control != nullptr) { |
|||
GetMetadataFromControlNCA(patch, *control, icon, name); |
|||
} |
|||
|
|||
auto entry = MakeGameListEntry(file->GetFullPath(), |
|||
name, |
|||
file->GetSize(), |
|||
icon, |
|||
*loader, |
|||
program_id, |
|||
patch); |
|||
RecordEvent([=](GameListModel* game_list) { game_list->addEntry(entry, parent_dir); }); |
|||
} |
|||
} |
|||
|
|||
void GameListWorker::ScanFileSystem(ScanTarget target, |
|||
const std::string& dir_path, |
|||
bool deep_scan, |
|||
UISettings::GameDir& parent_dir) |
|||
{ |
|||
const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool { |
|||
if (stop_requested) { |
|||
// Breaks the callback loop.
|
|||
return false; |
|||
} |
|||
|
|||
const auto physical_name = Common::FS::PathToUTF8String(path); |
|||
const auto is_dir = Common::FS::IsDir(path); |
|||
|
|||
if (!is_dir |
|||
&& (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { |
|||
const auto file = QtCommon::vfs->OpenFile(physical_name, FileSys::OpenMode::Read); |
|||
if (!file) { |
|||
return true; |
|||
} |
|||
|
|||
auto loader = Loader::GetLoader(*QtCommon::system, file); |
|||
if (!loader) { |
|||
return true; |
|||
} |
|||
|
|||
const auto file_type = loader->GetFileType(); |
|||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { |
|||
return true; |
|||
} |
|||
|
|||
u64 program_id = 0; |
|||
const auto res2 = loader->ReadProgramId(program_id); |
|||
|
|||
if (target == ScanTarget::FillManualContentProvider) { |
|||
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { |
|||
QtCommon::provider->AddEntry(FileSys::TitleType::Application, |
|||
FileSys::GetCRTypeFromNCAType( |
|||
FileSys::NCA{file}.GetType()), |
|||
program_id, |
|||
file); |
|||
} else if (res2 == Loader::ResultStatus::Success |
|||
&& (file_type == Loader::FileType::XCI |
|||
|| file_type == Loader::FileType::NSP)) { |
|||
const auto nsp = file_type == Loader::FileType::NSP |
|||
? std::make_shared<FileSys::NSP>(file) |
|||
: FileSys::XCI{file}.GetSecurePartitionNSP(); |
|||
for (const auto& title : nsp->GetNCAs()) { |
|||
for (const auto& entry : title.second) { |
|||
QtCommon::provider->AddEntry(entry.first.first, |
|||
entry.first.second, |
|||
title.first, |
|||
entry.second->GetBaseFile()); |
|||
} |
|||
} |
|||
} |
|||
} else { |
|||
std::vector<u64> program_ids; |
|||
loader->ReadProgramIds(program_ids); |
|||
|
|||
if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 |
|||
&& (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { |
|||
for (const auto id : program_ids) { |
|||
loader = Loader::GetLoader(*QtCommon::system, file, id); |
|||
if (!loader) { |
|||
continue; |
|||
} |
|||
|
|||
std::vector<u8> icon; |
|||
[[maybe_unused]] const auto res1 = loader->ReadIcon(icon); |
|||
|
|||
std::string name = " "; |
|||
[[maybe_unused]] const auto res3 = loader->ReadTitle(name); |
|||
|
|||
const FileSys::PatchManager patch{id, |
|||
QtCommon::system->GetFileSystemController(), |
|||
QtCommon::system->GetContentProvider()}; |
|||
|
|||
auto entry = MakeGameListEntry(physical_name, |
|||
name, |
|||
Common::FS::GetSize(physical_name), |
|||
icon, |
|||
*loader, |
|||
id, |
|||
patch); |
|||
|
|||
RecordEvent([=](GameListModel* game_list) { |
|||
game_list->addEntry(entry, parent_dir); |
|||
}); |
|||
} |
|||
} else { |
|||
std::vector<u8> icon; |
|||
[[maybe_unused]] const auto res1 = loader->ReadIcon(icon); |
|||
|
|||
std::string name = " "; |
|||
[[maybe_unused]] const auto res3 = loader->ReadTitle(name); |
|||
|
|||
const FileSys::PatchManager patch{program_id, |
|||
QtCommon::system->GetFileSystemController(), |
|||
QtCommon::system->GetContentProvider()}; |
|||
|
|||
auto entry = MakeGameListEntry(physical_name, |
|||
name, |
|||
Common::FS::GetSize(physical_name), |
|||
icon, |
|||
*loader, |
|||
program_id, |
|||
patch); |
|||
|
|||
RecordEvent( |
|||
[=](GameListModel* game_list) { game_list->addEntry(entry, parent_dir); }); |
|||
} |
|||
} |
|||
} else if (is_dir) { |
|||
watch_list.append(QString::fromStdString(physical_name)); |
|||
} |
|||
|
|||
return true; |
|||
}; |
|||
|
|||
if (deep_scan) { |
|||
Common::FS::IterateDirEntriesRecursively(dir_path, |
|||
callback, |
|||
Common::FS::DirEntryFilter::All); |
|||
} else { |
|||
Common::FS::IterateDirEntries(dir_path, callback, Common::FS::DirEntryFilter::File); |
|||
} |
|||
} |
|||
|
|||
void GameListWorker::run() |
|||
{ |
|||
watch_list.clear(); |
|||
QtCommon::provider->ClearAllEntries(); |
|||
|
|||
const auto DirEntryReady = [&](UISettings::GameDir& game_list_dir) { |
|||
RecordEvent([=](GameListModel* game_list) { game_list->addDirEntry(game_list_dir); }); |
|||
}; |
|||
|
|||
for (UISettings::GameDir& game_dir : game_dirs) { |
|||
if (stop_requested) { |
|||
break; |
|||
} |
|||
|
|||
watch_list.append(QString::fromStdString(game_dir.path)); |
|||
DirEntryReady(game_dir); |
|||
ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan, |
|||
game_dir); |
|||
ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path, game_dir.deep_scan, |
|||
game_dir); |
|||
} |
|||
|
|||
RecordEvent([this](GameListModel* game_list) { game_list->donePopulating(watch_list); }); |
|||
processing_completed.Set(); |
|||
} |
|||
@ -0,0 +1,81 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <atomic> |
|||
#include <deque> |
|||
#include <memory> |
|||
#include <string> |
|||
|
|||
#include <QList> |
|||
#include <QObject> |
|||
#include <QRunnable> |
|||
#include <QString> |
|||
|
|||
#include "common/thread.h" |
|||
#include "core/file_sys/registered_cache.h" |
|||
#include "qt_common/uisettings.h" |
|||
|
|||
namespace Core { class System; } |
|||
|
|||
class GameListModel; |
|||
class QStandardItem; |
|||
|
|||
namespace FileSys { |
|||
class NCA; |
|||
class VfsFilesystem; |
|||
} // namespace FileSys |
|||
|
|||
/** |
|||
* Asynchronous worker object for populating the game list. |
|||
* Communicates with other threads through Qt's signal/slot system. |
|||
*/ |
|||
class GameListWorker : public QObject, public QRunnable { |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
explicit GameListWorker(QVector<UISettings::GameDir>& game_dirs_); |
|||
~GameListWorker() override; |
|||
|
|||
/// Starts the processing of directory tree information. |
|||
void run() override; |
|||
|
|||
public: |
|||
/** |
|||
* Synchronously processes any events queued by the worker. |
|||
* |
|||
* AddDirEntry is called on the game list for every discovered directory. |
|||
* AddEntry is called on the game list for every discovered program. |
|||
* DonePopulating is called on the game list when processing completes. |
|||
*/ |
|||
void ProcessEvents(GameListModel* game_list); |
|||
|
|||
signals: |
|||
void DataAvailable(); |
|||
|
|||
private: |
|||
template <typename F> |
|||
void RecordEvent(F&& func); |
|||
|
|||
private: |
|||
void AddTitlesToGameList(UISettings::GameDir& parent_dir); |
|||
|
|||
enum class ScanTarget { |
|||
FillManualContentProvider, |
|||
PopulateGameList, |
|||
}; |
|||
|
|||
void ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, |
|||
UISettings::GameDir& parent_dir); |
|||
|
|||
QVector<UISettings::GameDir>& game_dirs; |
|||
|
|||
QStringList watch_list; |
|||
|
|||
std::mutex lock; |
|||
std::condition_variable cv; |
|||
std::deque<std::function<void(GameListModel*)>> queued_events; |
|||
std::atomic_bool stop_requested = false; |
|||
Common::Event processing_completed; |
|||
}; |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue