refactor: Improve game list scanning with std::filesystem

Refactors the game list scanning code to use std::filesystem instead of custom
directory iteration functions. Main changes include:

- Replace Common::FS directory iteration with std::filesystem iterators
- Split game file processing logic into separate ProcessGameFile method
- Improve error handling with try-catch for filesystem operations
- Simplify control NCA metadata extraction
- Use more modern C++ features and cleaner code organization

This change should improve maintainability and reliability of the game
scanning system while potentially offering better performance through
native filesystem APIs.
This commit is contained in:
Zephyron 2025-01-06 14:41:06 +10:00
parent 3100d13fc0
commit 60cb826e93
No known key found for this signature in database
GPG key ID: 8DA271B6A74353F1
2 changed files with 99 additions and 96 deletions

View file

@ -335,112 +335,112 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
} }
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
GameListDir* parent_dir) { GameListDir* parent_dir) {
const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool { // Use std::filesystem for native directory iteration
if (stop_requested) { const std::filesystem::path root_path(dir_path);
// Breaks the callback loop.
return false;
}
const auto physical_name = Common::FS::PathToUTF8String(path); try {
const auto is_dir = Common::FS::IsDir(path); if (deep_scan) {
// Use recursive directory iterator for deep scanning
if (!is_dir && for (const auto& entry : std::filesystem::recursive_directory_iterator(root_path)) {
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { if (stop_requested) {
const auto file = vfs->OpenFile(physical_name, FileSys::OpenMode::Read); return;
if (!file) {
return true;
}
auto loader = Loader::GetLoader(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) {
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) {
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 && const auto physical_name = entry.path().string();
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { const bool is_dir = entry.is_directory();
for (const auto id : program_ids) {
loader = Loader::GetLoader(system, file, id);
if (!loader) {
continue;
}
std::vector<u8> icon; if (!is_dir && (HasSupportedFileExtension(physical_name) ||
[[maybe_unused]] const auto res1 = loader->ReadIcon(icon); IsExtractedNCAMain(physical_name))) {
ProcessGameFile(physical_name, target, parent_dir);
std::string name = " "; } else if (is_dir) {
[[maybe_unused]] const auto res3 = loader->ReadTitle(name); watch_list.append(QString::fromStdString(physical_name));
}
const FileSys::PatchManager patch{id, system.GetFileSystemController(), }
system.GetContentProvider()}; } else {
// Use regular directory iterator for shallow scanning
auto entry = MakeGameListEntry( for (const auto& entry : std::filesystem::directory_iterator(root_path)) {
physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, if (stop_requested) {
id, compatibility_list, play_time_manager, patch); return;
}
RecordEvent(
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); const auto physical_name = entry.path().string();
} const bool is_dir = entry.is_directory();
} else {
std::vector<u8> icon; if (!is_dir && (HasSupportedFileExtension(physical_name) ||
[[maybe_unused]] const auto res1 = loader->ReadIcon(icon); IsExtractedNCAMain(physical_name))) {
ProcessGameFile(physical_name, target, parent_dir);
std::string name = " "; } else if (is_dir) {
[[maybe_unused]] const auto res3 = loader->ReadTitle(name); watch_list.append(QString::fromStdString(physical_name));
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
system.GetContentProvider()};
auto entry = MakeGameListEntry(
physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
program_id, compatibility_list, play_time_manager, patch);
RecordEvent(
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
} }
} }
} else if (is_dir) {
watch_list.append(QString::fromStdString(physical_name));
} }
} catch (const std::filesystem::filesystem_error& e) {
LOG_ERROR(Frontend, "Error scanning directory {}: {}", dir_path, e.what());
}
}
return true; void GameListWorker::ProcessGameFile(const std::string& physical_name, ScanTarget target,
}; GameListDir* parent_dir) {
const auto file = vfs->OpenFile(physical_name, FileSys::OpenMode::Read);
if (!file) {
return;
}
if (deep_scan) { auto loader = Loader::GetLoader(system, file);
Common::FS::IterateDirEntriesRecursively(dir_path, callback, if (!loader) {
Common::FS::DirEntryFilter::All); return;
}
const auto file_type = loader->GetFileType();
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
return;
}
u64 program_id = 0;
if (loader->ReadProgramId(program_id) != Loader::ResultStatus::Success) {
return;
}
if (target == ScanTarget::FillManualContentProvider) {
if (file_type == Loader::FileType::NCA) {
provider->AddEntry(FileSys::TitleType::Application,
FileSys::GetCRTypeFromNCAType(FileSys::NCAContentType::Program),
program_id,
std::static_pointer_cast<FileSys::VfsFile>(file));
} else if (file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) {
const auto nsp = file_type == Loader::FileType::NSP
? std::make_shared<FileSys::NSP>(file)
: std::make_shared<FileSys::XCI>(file)
->GetSecurePartitionNSP();
for (const auto& title : nsp->GetNCAs()) {
for (const auto& entry : title.second) {
provider->AddEntry(entry.first.first, entry.first.second, title.first,
entry.second->GetBaseFile());
}
}
}
} else { } else {
Common::FS::IterateDirEntries(dir_path, callback, Common::FS::DirEntryFilter::File); std::vector<u8> icon;
std::string name;
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
system.GetContentProvider()};
const auto control = system.GetContentProvider().GetEntry(program_id, FileSys::ContentRecordType::Control);
if (control != nullptr) {
GetMetadataFromControlNCA(patch, *control, icon, name);
} else {
std::tie(icon, name) = std::make_pair(std::vector<u8>{}, "");
}
if (loader->ReadProgramId(program_id) == Loader::ResultStatus::Success) {
auto entry = MakeGameListEntry(physical_name, name, file->GetSize(), icon, *loader,
program_id, compatibility_list, play_time_manager, patch);
RecordEvent([entry, parent_dir](GameList* game_list) {
game_list->AddEntry(entry, parent_dir);
});
}
} }
} }

View file

@ -76,6 +76,9 @@ private:
void ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, void ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
GameListDir* parent_dir); GameListDir* parent_dir);
void ProcessGameFile(const std::string& physical_name, ScanTarget target,
GameListDir* parent_dir);
std::shared_ptr<FileSys::VfsFilesystem> vfs; std::shared_ptr<FileSys::VfsFilesystem> vfs;
FileSys::ManualContentProvider* provider; FileSys::ManualContentProvider* provider;
QVector<UISettings::GameDir>& game_dirs; QVector<UISettings::GameDir>& game_dirs;