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,
GameListDir* parent_dir) {
const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool {
if (stop_requested) {
// Breaks the callback loop.
return false;
}
GameListDir* parent_dir) {
// Use std::filesystem for native directory iteration
const std::filesystem::path root_path(dir_path);
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 = vfs->OpenFile(physical_name, FileSys::OpenMode::Read);
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());
}
}
try {
if (deep_scan) {
// Use recursive directory iterator for deep scanning
for (const auto& entry : std::filesystem::recursive_directory_iterator(root_path)) {
if (stop_requested) {
return;
}
} 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(system, file, id);
if (!loader) {
continue;
}
const auto physical_name = entry.path().string();
const bool is_dir = entry.is_directory();
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, system.GetFileSystemController(),
system.GetContentProvider()};
auto entry = MakeGameListEntry(
physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
id, compatibility_list, play_time_manager, patch);
RecordEvent(
[=](GameList* 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, 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); });
if (!is_dir && (HasSupportedFileExtension(physical_name) ||
IsExtractedNCAMain(physical_name))) {
ProcessGameFile(physical_name, target, parent_dir);
} else if (is_dir) {
watch_list.append(QString::fromStdString(physical_name));
}
}
} else {
// Use regular directory iterator for shallow scanning
for (const auto& entry : std::filesystem::directory_iterator(root_path)) {
if (stop_requested) {
return;
}
const auto physical_name = entry.path().string();
const bool is_dir = entry.is_directory();
if (!is_dir && (HasSupportedFileExtension(physical_name) ||
IsExtractedNCAMain(physical_name))) {
ProcessGameFile(physical_name, target, parent_dir);
} else if (is_dir) {
watch_list.append(QString::fromStdString(physical_name));
}
}
} 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) {
Common::FS::IterateDirEntriesRecursively(dir_path, callback,
Common::FS::DirEntryFilter::All);
auto loader = Loader::GetLoader(system, file);
if (!loader) {
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 {
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,
GameListDir* parent_dir);
void ProcessGameFile(const std::string& physical_name, ScanTarget target,
GameListDir* parent_dir);
std::shared_ptr<FileSys::VfsFilesystem> vfs;
FileSys::ManualContentProvider* provider;
QVector<UISettings::GameDir>& game_dirs;