2017-10-20 11:26:15 -04:00
|
|
|
#include <cassert>
|
2025-03-11 12:48:26 -04:00
|
|
|
#include "AppConfig.h"
|
2017-10-20 11:26:15 -04:00
|
|
|
#include "string_format.h"
|
|
|
|
#include "PathUtils.h"
|
|
|
|
#include "BootablesDbClient.h"
|
|
|
|
#include "sqlite/SqliteStatement.h"
|
|
|
|
|
|
|
|
using namespace BootablesDb;
|
|
|
|
|
2024-10-17 21:40:04 +01:00
|
|
|
#define DATABASE_VERSION 3
|
2017-10-20 11:26:15 -04:00
|
|
|
|
|
|
|
static const char* g_dbFileName = "bootables.db";
|
|
|
|
|
2018-08-29 13:53:51 -04:00
|
|
|
static const char* g_bootablesTableCreateStatement =
|
|
|
|
"CREATE TABLE IF NOT EXISTS bootables"
|
|
|
|
"("
|
|
|
|
" path TEXT PRIMARY KEY,"
|
|
|
|
" discId VARCHAR(10) DEFAULT '',"
|
|
|
|
" title TEXT DEFAULT '',"
|
|
|
|
" coverUrl TEXT DEFAULT '',"
|
2019-01-05 22:57:08 +00:00
|
|
|
" lastBootedTime INTEGER DEFAULT 0,"
|
2024-10-17 21:40:04 +01:00
|
|
|
" overview TEXT DEFAULT '',"
|
|
|
|
" bootableType INTEGER DEFAULT 0"
|
2018-08-29 13:53:51 -04:00
|
|
|
")";
|
2017-10-20 11:26:15 -04:00
|
|
|
|
|
|
|
CClient::CClient()
|
|
|
|
{
|
|
|
|
m_dbPath = CAppConfig::GetInstance().GetBasePath() / g_dbFileName;
|
|
|
|
|
2024-10-17 21:40:04 +01:00
|
|
|
UpgradeDb();
|
2017-10-20 11:26:15 -04:00
|
|
|
|
|
|
|
m_db = Framework::CSqliteDb(Framework::PathUtils::GetNativeStringFromPath(m_dbPath).c_str(),
|
2018-08-29 13:53:51 -04:00
|
|
|
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
|
2017-10-20 11:26:15 -04:00
|
|
|
|
|
|
|
{
|
|
|
|
auto query = string_format("PRAGMA user_version = %d", DATABASE_VERSION);
|
|
|
|
Framework::CSqliteStatement statement(m_db, query.c_str());
|
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(m_db, g_bootablesTableCreateStatement);
|
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
2022-02-19 00:40:29 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
auto path = Framework::PathUtils::GetAppResourcesPath() / "states.db";
|
|
|
|
std::error_code errorCode;
|
|
|
|
if(fs::exists(path, errorCode))
|
|
|
|
{
|
|
|
|
char* err = NULL;
|
|
|
|
auto query = string_format("ATTACH DATABASE '%s' as 'stateDB';", path.string().c_str());
|
|
|
|
sqlite3_exec(m_db, query.c_str(), 0, 0, &err);
|
|
|
|
m_attachedState = err == NULL;
|
|
|
|
}
|
|
|
|
}
|
2017-10-20 11:26:15 -04:00
|
|
|
}
|
|
|
|
|
2020-12-15 13:33:59 -05:00
|
|
|
bool CClient::BootableExists(const fs::path& path)
|
2020-02-18 11:12:05 +00:00
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(m_db, "SELECT * FROM bootables WHERE path = ?");
|
|
|
|
statement.BindText(1, Framework::PathUtils::GetNativeStringFromPath(path).c_str());
|
|
|
|
return statement.Step();
|
|
|
|
}
|
|
|
|
|
2019-10-16 20:51:11 -04:00
|
|
|
Bootable CClient::GetBootable(const fs::path& path)
|
2017-10-20 11:26:15 -04:00
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(m_db, "SELECT * FROM bootables WHERE path = ?");
|
|
|
|
statement.BindText(1, Framework::PathUtils::GetNativeStringFromPath(path).c_str());
|
|
|
|
statement.StepWithResult();
|
2017-10-20 12:23:10 -04:00
|
|
|
return ReadBootable(statement);
|
2017-10-20 11:26:15 -04:00
|
|
|
}
|
|
|
|
|
2019-02-11 13:23:13 -05:00
|
|
|
std::vector<Bootable> CClient::GetBootables(int32_t sortMethod)
|
2017-10-20 11:26:15 -04:00
|
|
|
{
|
2019-01-06 13:44:16 +00:00
|
|
|
std::string query = "SELECT * FROM bootables ";
|
|
|
|
|
2019-02-11 13:23:13 -05:00
|
|
|
switch(sortMethod)
|
2019-01-06 13:44:16 +00:00
|
|
|
{
|
2019-02-11 13:23:13 -05:00
|
|
|
case SORT_METHOD_RECENT:
|
2019-01-06 12:27:31 +00:00
|
|
|
query += "WHERE lastBootedTime != 0 Order By lastBootedTime DESC ";
|
|
|
|
break;
|
2019-02-11 13:23:13 -05:00
|
|
|
case SORT_METHOD_HOMEBREW:
|
2019-01-06 12:27:31 +00:00
|
|
|
query += "WHERE path LIKE '%.elf' COLLATE NOCASE ";
|
|
|
|
break;
|
2022-09-29 08:27:05 -04:00
|
|
|
case SORT_METHOD_ARCADE:
|
2022-12-11 16:23:09 -05:00
|
|
|
query += "WHERE path LIKE '%.arcadedef' COLLATE NOCASE ORDER BY title";
|
2022-09-29 08:27:05 -04:00
|
|
|
break;
|
2019-02-11 13:23:13 -05:00
|
|
|
case SORT_METHOD_NONE:
|
|
|
|
break;
|
2019-01-06 13:44:16 +00:00
|
|
|
}
|
2017-10-20 11:26:15 -04:00
|
|
|
std::vector<Bootable> bootables;
|
|
|
|
|
2019-01-06 13:44:16 +00:00
|
|
|
Framework::CSqliteStatement statement(m_db, query.c_str());
|
2017-10-20 11:26:15 -04:00
|
|
|
while(statement.Step())
|
|
|
|
{
|
2017-10-20 12:23:10 -04:00
|
|
|
auto bootable = ReadBootable(statement);
|
2017-10-20 11:26:15 -04:00
|
|
|
bootables.push_back(bootable);
|
|
|
|
}
|
|
|
|
|
|
|
|
return bootables;
|
|
|
|
}
|
|
|
|
|
2024-10-22 11:11:49 +01:00
|
|
|
void CClient::RegisterBootable(const fs::path& path, const char* title, const char* discId, BootableUtils::BOOTABLE_TYPE bootableType)
|
2017-10-20 11:26:15 -04:00
|
|
|
{
|
2024-10-17 21:40:04 +01:00
|
|
|
Framework::CSqliteStatement statement(m_db, "INSERT OR IGNORE INTO bootables (path, title, discId, bootableType) VALUES (?,?,?,?)");
|
2017-10-20 11:26:15 -04:00
|
|
|
statement.BindText(1, Framework::PathUtils::GetNativeStringFromPath(path).c_str());
|
2019-01-06 01:21:30 +00:00
|
|
|
statement.BindText(2, title, true);
|
2019-01-06 14:09:34 +00:00
|
|
|
statement.BindText(3, discId, true);
|
2024-10-17 21:40:04 +01:00
|
|
|
statement.BindInteger(4, bootableType);
|
2017-10-20 11:26:15 -04:00
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
|
|
|
|
2019-10-16 20:51:11 -04:00
|
|
|
void CClient::UnregisterBootable(const fs::path& path)
|
2017-10-27 18:06:59 -04:00
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(m_db, "DELETE FROM bootables WHERE path = ?");
|
|
|
|
statement.BindText(1, Framework::PathUtils::GetNativeStringFromPath(path).c_str());
|
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
|
|
|
|
2019-10-16 20:51:11 -04:00
|
|
|
void CClient::SetDiscId(const fs::path& path, const char* discId)
|
2017-10-20 11:26:15 -04:00
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(m_db, "UPDATE bootables SET discId = ? WHERE path = ?");
|
|
|
|
statement.BindText(1, discId, true);
|
|
|
|
statement.BindText(2, Framework::PathUtils::GetNativeStringFromPath(path).c_str());
|
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
|
|
|
|
2019-10-16 20:51:11 -04:00
|
|
|
void CClient::SetTitle(const fs::path& path, const char* title)
|
2017-10-20 12:23:10 -04:00
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(m_db, "UPDATE bootables SET title = ? WHERE path = ?");
|
|
|
|
statement.BindText(1, title, true);
|
|
|
|
statement.BindText(2, Framework::PathUtils::GetNativeStringFromPath(path).c_str());
|
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
|
|
|
|
2019-10-16 20:51:11 -04:00
|
|
|
void CClient::SetCoverUrl(const fs::path& path, const char* coverUrl)
|
2017-10-23 07:27:42 -04:00
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(m_db, "UPDATE bootables SET coverUrl = ? WHERE path = ?");
|
|
|
|
statement.BindText(1, coverUrl, true);
|
|
|
|
statement.BindText(2, Framework::PathUtils::GetNativeStringFromPath(path).c_str());
|
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
|
|
|
|
2019-10-16 20:51:11 -04:00
|
|
|
void CClient::SetLastBootedTime(const fs::path& path, time_t lastBootedTime)
|
2017-10-20 11:26:15 -04:00
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(m_db, "UPDATE bootables SET lastBootedTime = ? WHERE path = ?");
|
|
|
|
statement.BindInteger(1, lastBootedTime);
|
|
|
|
statement.BindText(2, Framework::PathUtils::GetNativeStringFromPath(path).c_str());
|
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
|
|
|
|
2019-10-16 20:51:11 -04:00
|
|
|
void CClient::SetOverview(const fs::path& path, const char* overview)
|
2019-01-05 22:57:08 +00:00
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(m_db, "UPDATE bootables SET overview = ? WHERE path = ?");
|
|
|
|
statement.BindText(1, overview, true);
|
|
|
|
statement.BindText(2, Framework::PathUtils::GetNativeStringFromPath(path).c_str());
|
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
|
|
|
|
2022-02-19 00:40:29 +00:00
|
|
|
BootableStateList CClient::GetGameStates(std::string discId)
|
|
|
|
{
|
|
|
|
BootableStateList states;
|
|
|
|
if(!m_attachedState)
|
|
|
|
return states;
|
|
|
|
|
|
|
|
std::string query = "SELECT stateDB.labels.name AS stateName, stateDB.labels.color AS stateColor FROM stateDB.games ";
|
|
|
|
query += "LEFT JOIN stateDB.labels ON stateDB.games.state = stateDB.labels.name ";
|
2022-02-24 15:32:35 +00:00
|
|
|
query += "WHERE stateDB.games.discId = ?;";
|
2022-02-19 00:40:29 +00:00
|
|
|
|
|
|
|
Framework::CSqliteStatement statement(m_db, query.c_str());
|
|
|
|
statement.BindText(1, discId.c_str(), true);
|
|
|
|
while(statement.Step())
|
|
|
|
{
|
|
|
|
BootableState state;
|
|
|
|
state.name = reinterpret_cast<const char*>(sqlite3_column_text(statement, 0));
|
|
|
|
state.color = reinterpret_cast<const char*>(sqlite3_column_text(statement, 1));
|
|
|
|
states.push_back(state);
|
|
|
|
}
|
|
|
|
return states;
|
|
|
|
}
|
|
|
|
|
2022-02-19 00:03:53 +00:00
|
|
|
BootableStateList CClient::GetStates()
|
|
|
|
{
|
|
|
|
BootableStateList states;
|
|
|
|
if(!m_attachedState)
|
|
|
|
return states;
|
|
|
|
|
|
|
|
std::string query = "SELECT stateDB.labels.name AS stateName, stateDB.labels.color AS stateColor FROM stateDB.labels;";
|
|
|
|
|
|
|
|
Framework::CSqliteStatement statement(m_db, query.c_str());
|
|
|
|
while(statement.Step())
|
|
|
|
{
|
|
|
|
BootableState state;
|
|
|
|
state.name = reinterpret_cast<const char*>(sqlite3_column_text(statement, 0));
|
|
|
|
state.color = reinterpret_cast<const char*>(sqlite3_column_text(statement, 1));
|
|
|
|
states.push_back(state);
|
|
|
|
}
|
|
|
|
return states;
|
|
|
|
}
|
|
|
|
|
2017-10-20 12:23:10 -04:00
|
|
|
Bootable CClient::ReadBootable(Framework::CSqliteStatement& statement)
|
|
|
|
{
|
|
|
|
Bootable bootable;
|
2018-08-29 13:53:51 -04:00
|
|
|
bootable.path = Framework::PathUtils::GetPathFromNativeString(reinterpret_cast<const char*>(sqlite3_column_text(statement, 0)));
|
|
|
|
bootable.discId = reinterpret_cast<const char*>(sqlite3_column_text(statement, 1));
|
|
|
|
bootable.title = reinterpret_cast<const char*>(sqlite3_column_text(statement, 2));
|
2019-01-06 12:27:31 +00:00
|
|
|
bootable.coverUrl = reinterpret_cast<const char*>(sqlite3_column_text(statement, 3));
|
|
|
|
bootable.overview = reinterpret_cast<const char*>(sqlite3_column_text(statement, 5));
|
2017-10-20 12:23:10 -04:00
|
|
|
bootable.lastBootedTime = sqlite3_column_int(statement, 4);
|
2022-02-19 00:40:29 +00:00
|
|
|
bootable.states = GetGameStates(bootable.discId);
|
2024-10-22 11:11:49 +01:00
|
|
|
bootable.bootableType = static_cast<BootableUtils::BOOTABLE_TYPE>(sqlite3_column_int(statement, 6));
|
2017-10-20 12:23:10 -04:00
|
|
|
return bootable;
|
|
|
|
}
|
|
|
|
|
2024-10-17 21:40:04 +01:00
|
|
|
void CClient::UpgradeDb()
|
2017-10-20 11:26:15 -04:00
|
|
|
{
|
2024-10-17 21:40:04 +01:00
|
|
|
try
|
|
|
|
{
|
|
|
|
auto db = Framework::CSqliteDb(Framework::PathUtils::GetNativeStringFromPath(m_dbPath).c_str(), SQLITE_OPEN_READWRITE);
|
|
|
|
|
|
|
|
Framework::CSqliteStatement statement(db, "PRAGMA user_version");
|
|
|
|
statement.StepWithResult();
|
|
|
|
|
|
|
|
auto currentVersion = sqlite3_column_int(statement, 0);
|
|
|
|
while(currentVersion < DATABASE_VERSION)
|
|
|
|
{
|
|
|
|
switch(currentVersion)
|
|
|
|
{
|
|
|
|
case 2:
|
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(db, "ALTER TABLE bootables ADD COLUMN bootableType INTEGER DEFAULT 0");
|
|
|
|
statement.StepNoResult();
|
|
|
|
|
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(db, "UPDATE bootables SET bootableType = ? WHERE path LIKE '%.arcade%';");
|
2024-10-22 11:11:49 +01:00
|
|
|
statement.BindInteger(1, BootableUtils::PS2_ARCADE);
|
2024-10-17 21:40:04 +01:00
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(db, "UPDATE bootables SET bootableType = ? WHERE path LIKE '%.elf%';");
|
2024-10-22 11:11:49 +01:00
|
|
|
statement.BindInteger(1, BootableUtils::PS2_ELF);
|
2024-10-17 21:40:04 +01:00
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
|
|
|
{
|
|
|
|
Framework::CSqliteStatement statement(db, "UPDATE bootables SET bootableType = ? WHERE bootableType = 0;");
|
2024-10-22 11:11:49 +01:00
|
|
|
statement.BindInteger(1, BootableUtils::PS2_DISC);
|
2024-10-17 21:40:04 +01:00
|
|
|
statement.StepNoResult();
|
|
|
|
}
|
|
|
|
|
|
|
|
currentVersion = 3;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fs::remove(m_dbPath);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto query = string_format("PRAGMA user_version = %d", currentVersion);
|
|
|
|
Framework::CSqliteStatement versionStatement(db, query.c_str());
|
|
|
|
versionStatement.StepNoResult();
|
|
|
|
}
|
|
|
|
catch(...)
|
2017-10-20 11:26:15 -04:00
|
|
|
{
|
2019-10-16 20:51:11 -04:00
|
|
|
fs::remove(m_dbPath);
|
2017-10-20 11:26:15 -04:00
|
|
|
}
|
|
|
|
}
|