2017-11-01 07:26:08 -04:00
|
|
|
#include <algorithm>
|
2019-01-16 20:00:10 +00:00
|
|
|
#include "AppConfig.h"
|
2017-10-20 11:26:15 -04:00
|
|
|
#include "BootablesProcesses.h"
|
2017-10-20 15:38:51 -04:00
|
|
|
#include "BootablesDbClient.h"
|
2017-10-23 07:27:42 -04:00
|
|
|
#include "TheGamesDbClient.h"
|
2017-10-20 11:26:15 -04:00
|
|
|
#include "DiskUtils.h"
|
2019-01-16 20:00:10 +00:00
|
|
|
#include "PathUtils.h"
|
2017-10-23 07:27:42 -04:00
|
|
|
#include "string_format.h"
|
2019-10-16 20:51:11 -04:00
|
|
|
#include "StdStreamUtils.h"
|
2019-01-16 20:00:10 +00:00
|
|
|
#include "http/HttpClientFactory.h"
|
2017-10-20 11:26:15 -04:00
|
|
|
|
|
|
|
//Jobs
|
|
|
|
// Scan for new games (from input directory)
|
|
|
|
// Remove games that might not be available anymore
|
|
|
|
// Extract game ids from disk images
|
|
|
|
// Pull disc cover URLs and titles from GamesDb/TheGamesDb
|
|
|
|
|
2020-05-03 16:13:54 -04:00
|
|
|
//#define SCAN_LOG
|
|
|
|
|
2020-04-23 15:35:32 -04:00
|
|
|
static void BootableLog(const char* format, ...)
|
|
|
|
{
|
2020-05-03 16:13:54 -04:00
|
|
|
#ifdef SCAN_LOG
|
2020-04-23 15:35:32 -04:00
|
|
|
static FILE* logStream = nullptr;
|
|
|
|
if(!logStream)
|
|
|
|
{
|
|
|
|
auto logPath = CAppConfig::GetBasePath() / "bootables.log";
|
|
|
|
logStream = fopen(logPath.string().c_str(), "wb");
|
|
|
|
}
|
|
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
|
|
vfprintf(logStream, format, args);
|
|
|
|
va_end(args);
|
|
|
|
fflush(logStream);
|
2020-05-03 16:13:54 -04:00
|
|
|
#endif
|
2020-04-23 15:35:32 -04:00
|
|
|
}
|
|
|
|
|
2019-10-16 20:51:11 -04:00
|
|
|
bool IsBootableExecutablePath(const fs::path& filePath)
|
2017-11-01 07:26:08 -04:00
|
|
|
{
|
|
|
|
auto extension = filePath.extension().string();
|
|
|
|
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
|
|
|
return (extension == ".elf");
|
|
|
|
}
|
|
|
|
|
2019-10-16 20:51:11 -04:00
|
|
|
bool IsBootableDiscImagePath(const fs::path& filePath)
|
2017-11-01 07:26:08 -04:00
|
|
|
{
|
2020-10-04 11:47:32 -04:00
|
|
|
const auto& supportedExtensions = DiskUtils::GetSupportedExtensions();
|
2017-11-01 07:26:08 -04:00
|
|
|
auto extension = filePath.extension().string();
|
|
|
|
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
2020-10-04 11:47:32 -04:00
|
|
|
auto extensionIterator = supportedExtensions.find(extension);
|
|
|
|
return extensionIterator != std::end(supportedExtensions);
|
2017-11-01 07:26:08 -04:00
|
|
|
}
|
|
|
|
|
2021-08-04 13:47:28 -04:00
|
|
|
bool DoesBootableExist(const fs::path& filePath)
|
|
|
|
{
|
2021-08-10 17:36:31 -04:00
|
|
|
//TODO: Properly support S3 paths. Also, beware when implementing this because Android
|
|
|
|
// might complain about network access being done on the main thread.
|
|
|
|
static const char* s3ImagePathPrefix = "//s3/";
|
|
|
|
if(filePath.string().find(s3ImagePathPrefix) == 0) return true;
|
2021-08-04 13:47:28 -04:00
|
|
|
return fs::exists(filePath);
|
|
|
|
}
|
|
|
|
|
2020-12-15 13:33:59 -05:00
|
|
|
bool TryRegisterBootable(const fs::path& path)
|
2019-01-16 19:57:55 +00:00
|
|
|
{
|
2020-12-15 13:33:59 -05:00
|
|
|
try
|
|
|
|
{
|
|
|
|
std::string serial;
|
|
|
|
if(
|
|
|
|
!BootablesDb::CClient::GetInstance().BootableExists(path) &&
|
|
|
|
!IsBootableExecutablePath(path) &&
|
|
|
|
!(IsBootableDiscImagePath(path) && DiskUtils::TryGetDiskId(path, &serial)))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
BootablesDb::CClient::GetInstance().RegisterBootable(path, path.filename().string().c_str(), serial.c_str());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch(...)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TryUpdateLastBootedTime(const fs::path& path)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
BootablesDb::CClient::GetInstance().SetLastBootedTime(path, std::time(nullptr));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch(...)
|
2019-01-16 19:57:55 +00:00
|
|
|
{
|
2020-03-11 10:35:30 +00:00
|
|
|
return false;
|
2019-01-16 19:57:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-16 20:51:11 -04:00
|
|
|
void ScanBootables(const fs::path& parentPath, bool recursive)
|
2017-10-20 11:26:15 -04:00
|
|
|
{
|
2020-04-23 15:35:32 -04:00
|
|
|
BootableLog("Entering ScanBootables(path = '%s', recursive = %d);\r\n",
|
2020-05-03 16:13:16 -04:00
|
|
|
parentPath.string().c_str(), static_cast<int>(recursive));
|
2020-04-26 15:24:43 -04:00
|
|
|
try
|
2017-10-20 11:26:15 -04:00
|
|
|
{
|
2020-04-29 19:43:25 -04:00
|
|
|
std::error_code ec;
|
|
|
|
for(auto pathIterator = fs::directory_iterator(parentPath, ec);
|
2020-05-03 16:13:16 -04:00
|
|
|
pathIterator != fs::directory_iterator(); pathIterator.increment(ec))
|
2017-10-20 11:26:15 -04:00
|
|
|
{
|
2020-04-26 15:24:43 -04:00
|
|
|
auto& path = pathIterator->path();
|
|
|
|
BootableLog("Checking '%s'... ", path.string().c_str());
|
|
|
|
try
|
2017-11-11 11:58:21 -05:00
|
|
|
{
|
2020-04-29 19:43:25 -04:00
|
|
|
if(ec)
|
|
|
|
{
|
|
|
|
BootableLog(" failed to get status: %s.\r\n", ec.message().c_str());
|
|
|
|
continue;
|
|
|
|
}
|
2020-04-26 15:24:43 -04:00
|
|
|
if(recursive && fs::is_directory(path))
|
|
|
|
{
|
|
|
|
BootableLog("is directory.\r\n");
|
|
|
|
ScanBootables(path, recursive);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
BootableLog("registering... ");
|
2020-12-15 13:33:59 -05:00
|
|
|
bool success = TryRegisterBootable(path);
|
2020-04-26 15:24:43 -04:00
|
|
|
BootableLog("result = %d\r\n", static_cast<int>(success));
|
|
|
|
}
|
|
|
|
catch(const std::exception& exception)
|
|
|
|
{
|
|
|
|
//Failed to process a path, keep going
|
|
|
|
BootableLog(" exception: %s\r\n", exception.what());
|
2017-11-11 11:58:21 -05:00
|
|
|
}
|
2017-11-01 07:26:30 -04:00
|
|
|
}
|
2017-10-20 11:26:15 -04:00
|
|
|
}
|
2020-04-26 15:24:43 -04:00
|
|
|
catch(const std::exception& exception)
|
|
|
|
{
|
|
|
|
BootableLog("Caught an exception while trying to list directory: %s\r\n", exception.what());
|
|
|
|
}
|
2020-04-23 15:35:32 -04:00
|
|
|
BootableLog("Exiting ScanBootables(path = '%s', recursive = %d);\r\n",
|
2020-05-03 16:13:16 -04:00
|
|
|
parentPath.string().c_str(), static_cast<int>(recursive));
|
2017-10-20 11:26:15 -04:00
|
|
|
}
|
|
|
|
|
2019-10-16 20:51:11 -04:00
|
|
|
std::set<fs::path> GetActiveBootableDirectories()
|
2017-11-24 18:31:04 -05:00
|
|
|
{
|
2019-10-16 20:51:11 -04:00
|
|
|
std::set<fs::path> result;
|
2017-11-24 18:31:04 -05:00
|
|
|
auto bootables = BootablesDb::CClient::GetInstance().GetBootables();
|
|
|
|
for(const auto& bootable : bootables)
|
|
|
|
{
|
|
|
|
auto parentPath = bootable.path.parent_path();
|
2020-03-07 17:58:03 +00:00
|
|
|
static const char* s3ImagePathPrefix = "//s3/";
|
|
|
|
if(parentPath.string().find(s3ImagePathPrefix) == std::string::npos)
|
|
|
|
result.insert(parentPath);
|
2017-11-24 18:31:04 -05:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-10-27 18:06:59 -04:00
|
|
|
void PurgeInexistingFiles()
|
|
|
|
{
|
|
|
|
auto bootables = BootablesDb::CClient::GetInstance().GetBootables();
|
|
|
|
for(const auto& bootable : bootables)
|
|
|
|
{
|
2021-08-04 13:47:28 -04:00
|
|
|
if(DoesBootableExist(bootable.path)) continue;
|
2017-10-27 18:06:59 -04:00
|
|
|
BootablesDb::CClient::GetInstance().UnregisterBootable(bootable.path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-20 11:26:15 -04:00
|
|
|
void FetchGameTitles()
|
|
|
|
{
|
2017-10-20 15:40:49 -04:00
|
|
|
auto bootables = BootablesDb::CClient::GetInstance().GetBootables();
|
2019-01-06 00:04:03 +00:00
|
|
|
std::vector<std::string> serials;
|
2017-10-20 15:40:49 -04:00
|
|
|
for(const auto& bootable : bootables)
|
|
|
|
{
|
2019-01-28 20:11:23 -05:00
|
|
|
if(bootable.discId.empty()) continue;
|
2019-01-06 00:04:03 +00:00
|
|
|
|
|
|
|
if(bootable.coverUrl.empty() || bootable.title.empty() || bootable.overview.empty())
|
2019-01-28 20:11:23 -05:00
|
|
|
{
|
2019-01-06 00:04:03 +00:00
|
|
|
serials.push_back(bootable.discId);
|
2019-01-28 20:11:23 -05:00
|
|
|
}
|
2019-01-06 00:04:03 +00:00
|
|
|
}
|
|
|
|
|
2019-01-28 20:11:23 -05:00
|
|
|
if(serials.empty()) return;
|
2019-01-06 00:04:03 +00:00
|
|
|
|
2019-01-26 23:01:51 -05:00
|
|
|
try
|
2019-01-06 00:04:03 +00:00
|
|
|
{
|
2019-01-26 23:01:51 -05:00
|
|
|
auto gamesList = TheGamesDb::CClient::GetInstance().GetGames(serials);
|
|
|
|
for(auto& game : gamesList)
|
2017-10-20 15:40:49 -04:00
|
|
|
{
|
2019-01-26 23:01:51 -05:00
|
|
|
for(const auto& bootable : bootables)
|
2019-01-06 00:04:03 +00:00
|
|
|
{
|
2019-01-26 23:01:51 -05:00
|
|
|
for(const auto& discId : game.discIds)
|
2019-01-06 00:04:03 +00:00
|
|
|
{
|
2019-01-26 23:01:51 -05:00
|
|
|
if(discId == bootable.discId)
|
2019-01-06 00:04:03 +00:00
|
|
|
{
|
2019-01-26 23:01:51 -05:00
|
|
|
BootablesDb::CClient::GetInstance().SetTitle(bootable.path, game.title.c_str());
|
2019-01-06 00:04:03 +00:00
|
|
|
|
2019-01-26 23:01:51 -05:00
|
|
|
if(!game.overview.empty())
|
|
|
|
{
|
|
|
|
BootablesDb::CClient::GetInstance().SetOverview(bootable.path, game.overview.c_str());
|
|
|
|
}
|
|
|
|
if(!game.boxArtUrl.empty())
|
|
|
|
{
|
2019-05-10 09:27:13 +01:00
|
|
|
auto coverUrl = string_format("%s%s", game.baseImgUrl.c_str(), game.boxArtUrl.c_str());
|
2019-01-26 23:01:51 -05:00
|
|
|
BootablesDb::CClient::GetInstance().SetCoverUrl(bootable.path, coverUrl.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2019-01-06 00:04:03 +00:00
|
|
|
}
|
|
|
|
}
|
2017-10-20 15:40:49 -04:00
|
|
|
}
|
|
|
|
}
|
2019-01-26 23:01:51 -05:00
|
|
|
catch(...)
|
|
|
|
{
|
|
|
|
}
|
2017-10-20 11:26:15 -04:00
|
|
|
}
|
2019-01-16 20:00:10 +00:00
|
|
|
|
|
|
|
void FetchGameCovers()
|
|
|
|
{
|
2019-10-16 20:51:11 -04:00
|
|
|
auto coverpath(CAppConfig::GetBasePath() / fs::path("covers"));
|
2019-01-16 20:00:10 +00:00
|
|
|
Framework::PathUtils::EnsurePathExists(coverpath);
|
|
|
|
|
|
|
|
auto bootables = BootablesDb::CClient::GetInstance().GetBootables();
|
|
|
|
std::vector<std::string> serials;
|
|
|
|
for(const auto& bootable : bootables)
|
|
|
|
{
|
|
|
|
if(bootable.discId.empty() || bootable.coverUrl.empty())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
auto path = coverpath / (bootable.discId + ".jpg");
|
2019-10-16 20:51:11 -04:00
|
|
|
if(fs::exists(path))
|
2019-01-16 20:00:10 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
auto requestResult =
|
2019-01-17 12:26:47 +00:00
|
|
|
[&]() {
|
|
|
|
auto client = Framework::Http::CreateHttpClient();
|
|
|
|
client->SetUrl(bootable.coverUrl);
|
|
|
|
return client->SendRequest();
|
|
|
|
}();
|
2019-01-16 20:00:10 +00:00
|
|
|
if(requestResult.statusCode == Framework::Http::HTTP_STATUS_CODE::OK)
|
|
|
|
{
|
2019-10-16 20:51:11 -04:00
|
|
|
auto outputStream = Framework::CreateOutputStdStream(path.native());
|
|
|
|
outputStream.Write(requestResult.data.GetBuffer(), requestResult.data.GetSize());
|
2019-01-16 20:00:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|