[refactor] Separate internal and external DB functions

This commit is contained in:
dongresource 2021-03-15 00:35:54 +01:00
parent ec67cc6527
commit df1ac82300
2 changed files with 172 additions and 174 deletions

View File

@ -22,47 +22,63 @@
#include <mutex> #include <mutex>
#endif #endif
std::mutex dbCrit; using namespace Database;
sqlite3* db;
void Database::open() { static std::mutex dbCrit;
int rc = sqlite3_open(settings::DBPATH.c_str(), &db); static sqlite3* db;
if (rc != SQLITE_OK) {
std::cout << "[FATAL] Cannot open database: " << sqlite3_errmsg(db) << std::endl; static void createMetaTable() {
std::lock_guard<std::mutex> lock(dbCrit); // XXX
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
const char* sql = R"(
CREATE TABLE Meta(
Key TEXT NOT NULL UNIQUE,
Value INTEGER NOT NULL
);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
exit(1);
}
sqlite3_finalize(stmt);
sql = R"(
INSERT INTO Meta (Key, Value)
VALUES (?, ?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, "ProtocolVersion", -1, NULL);
sqlite3_bind_int(stmt, 2, PROTOCOL_VERSION);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
exit(1); exit(1);
} }
// foreign keys in sqlite are off by default; enable them sqlite3_reset(stmt);
sqlite3_exec(db, "PRAGMA foreign_keys=ON;", NULL, NULL, NULL); sqlite3_bind_text(stmt, 1, "DatabaseVersion", -1, NULL);
sqlite3_bind_int(stmt, 2, DATABASE_VERSION);
// just in case a DB operation collides with an external manual modification int rc = sqlite3_step(stmt);
sqlite3_busy_timeout(db, 2000); sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
checkMetaTable(); std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl;
createTables(); sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
exit(1);
std::cout << "[INFO] Database in operation ";
int accounts = getTableSize("Accounts");
int players = getTableSize("Players");
std::string message = "";
if (accounts > 0) {
message += ": Found " + std::to_string(accounts) + " Account";
if (accounts > 1)
message += "s";
} }
if (players > 0) {
message += " and " + std::to_string(players) + " Player Character"; sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
if (players > 1) std::cout << "[INFO] Created new meta table" << std::endl;
message += "s";
}
std::cout << message << std::endl;
} }
void Database::close() { static void checkMetaTable() {
sqlite3_close(db);
}
void Database::checkMetaTable() {
// first check if meta table exists // first check if meta table exists
const char* sql = R"( const char* sql = R"(
SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='Meta'; SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='Meta';
@ -170,58 +186,7 @@ void Database::checkMetaTable() {
} }
} }
void Database::createMetaTable() { static void createTables() {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
const char* sql = R"(
CREATE TABLE Meta(
Key TEXT NOT NULL UNIQUE,
Value INTEGER NOT NULL
);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
exit(1);
}
sqlite3_finalize(stmt);
sql = R"(
INSERT INTO Meta (Key, Value)
VALUES (?, ?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, "ProtocolVersion", -1, NULL);
sqlite3_bind_int(stmt, 2, PROTOCOL_VERSION);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
exit(1);
}
sqlite3_reset(stmt);
sqlite3_bind_text(stmt, 1, "DatabaseVersion", -1, NULL);
sqlite3_bind_int(stmt, 2, DATABASE_VERSION);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
exit(1);
}
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
std::cout << "[INFO] Created new meta table" << std::endl;
}
void Database::createTables() {
std::ifstream file("sql/tables.sql"); std::ifstream file("sql/tables.sql");
if (!file.is_open()) { if (!file.is_open()) {
std::cout << "[FATAL] Failed to open database scheme" << std::endl; std::cout << "[FATAL] Failed to open database scheme" << std::endl;
@ -241,8 +206,8 @@ void Database::createTables() {
} }
} }
int Database::getTableSize(std::string tableName) { static int getTableSize(std::string tableName) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit); // XXX
const char* sql = "SELECT COUNT(*) FROM ?"; const char* sql = "SELECT COUNT(*) FROM ?";
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
@ -254,6 +219,44 @@ int Database::getTableSize(std::string tableName) {
return result; return result;
} }
void Database::open() {
// XXX: move locks here
int rc = sqlite3_open(settings::DBPATH.c_str(), &db);
if (rc != SQLITE_OK) {
std::cout << "[FATAL] Cannot open database: " << sqlite3_errmsg(db) << std::endl;
exit(1);
}
// foreign keys in sqlite are off by default; enable them
sqlite3_exec(db, "PRAGMA foreign_keys=ON;", NULL, NULL, NULL);
// just in case a DB operation collides with an external manual modification
sqlite3_busy_timeout(db, 2000);
checkMetaTable();
createTables();
std::cout << "[INFO] Database in operation ";
int accounts = getTableSize("Accounts");
int players = getTableSize("Players");
std::string message = "";
if (accounts > 0) {
message += ": Found " + std::to_string(accounts) + " Account";
if (accounts > 1)
message += "s";
}
if (players > 0) {
message += " and " + std::to_string(players) + " Player Character";
if (players > 1)
message += "s";
}
std::cout << message << std::endl;
}
void Database::close() {
sqlite3_close(db);
}
void Database::findAccount(Account* account, std::string login) { void Database::findAccount(Account* account, std::string login) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
@ -304,8 +307,37 @@ int Database::addAccount(std::string login, std::string password) {
return sqlite3_last_insert_rowid(db); return sqlite3_last_insert_rowid(db);
} }
// NOTE: internal function; does not lock dbCrit static int getAccountIDFromPlayerID(int playerId, int *accountLevel=nullptr) {
bool Database::banAccount(int accountId, int days, std::string& reason) { const char *sql = R"(
SELECT Players.AccountID, AccountLevel
FROM Players
JOIN Accounts ON Players.AccountID = Accounts.AccountID
WHERE PlayerID = ?;
)";
sqlite3_stmt *stmt;
// get AccountID from PlayerID
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerId);
if (sqlite3_step(stmt) != SQLITE_ROW) {
std::cout << "[WARN] Database: failed to get AccountID from PlayerID: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
return -1;
}
int accountId = sqlite3_column_int(stmt, 0);
// optional secondary return value, for checking GM status
if (accountLevel != nullptr)
*accountLevel = sqlite3_column_int(stmt, 1);
sqlite3_finalize(stmt);
return accountId;
}
static bool banAccount(int accountId, int days, std::string& reason) {
const char* sql = R"( const char* sql = R"(
UPDATE Accounts SET UPDATE Accounts SET
BannedSince = (strftime('%s', 'now')), BannedSince = (strftime('%s', 'now')),
@ -331,8 +363,7 @@ bool Database::banAccount(int accountId, int days, std::string& reason) {
return true; return true;
} }
// NOTE: internal function; does not lock dbCrit static bool unbanAccount(int accountId) {
bool Database::unbanAccount(int accountId) {
const char* sql = R"( const char* sql = R"(
UPDATE Accounts SET UPDATE Accounts SET
BannedSince = 0, BannedSince = 0,
@ -391,36 +422,6 @@ bool Database::unbanPlayer(int playerId) {
return unbanAccount(accountId); return unbanAccount(accountId);
} }
int Database::getAccountIDFromPlayerID(int playerId, int *accountLevel) {
const char *sql = R"(
SELECT Players.AccountID, AccountLevel
FROM Players
JOIN Accounts ON Players.AccountID = Accounts.AccountID
WHERE PlayerID = ?;
)";
sqlite3_stmt *stmt;
// get AccountID from PlayerID
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerId);
if (sqlite3_step(stmt) != SQLITE_ROW) {
std::cout << "[WARN] Database: failed to get AccountID from PlayerID: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
return -1;
}
int accountId = sqlite3_column_int(stmt, 0);
// optional secondary return value, for checking GM status
if (accountLevel != nullptr)
*accountLevel = sqlite3_column_int(stmt, 1);
sqlite3_finalize(stmt);
return accountId;
}
void Database::updateSelected(int accountId, int slot) { void Database::updateSelected(int accountId, int slot) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
@ -904,6 +905,40 @@ bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) {
return rc == SQLITE_DONE; return rc == SQLITE_DONE;
} }
static void removeExpiredVehicles(Player* player) {
int32_t currentTime = getTimestamp();
// if there are expired vehicles in bank just remove them silently
for (int i = 0; i < ABANK_COUNT; i++) {
if (player->Bank[i].iType == 10 && player->Bank[i].iTimeLimit < currentTime && player->Bank[i].iTimeLimit != 0) {
memset(&player->Bank[i], 0, sizeof(sItemBase));
}
}
// we want to leave only 1 expired vehicle on player to delete it with the client packet
std::vector<sItemBase*> toRemove;
// equipped vehicle
if (player->Equip[8].iOpt > 0 && player->Equip[8].iTimeLimit < currentTime && player->Equip[8].iTimeLimit != 0) {
toRemove.push_back(&player->Equip[8]);
player->toRemoveVehicle.eIL = 0;
player->toRemoveVehicle.iSlotNum = 8;
}
// inventory
for (int i = 0; i < AINVEN_COUNT; i++) {
if (player->Inven[i].iType == 10 && player->Inven[i].iTimeLimit < currentTime && player->Inven[i].iTimeLimit != 0) {
toRemove.push_back(&player->Inven[i]);
player->toRemoveVehicle.eIL = 1;
player->toRemoveVehicle.iSlotNum = i;
}
}
// delete all but one vehicles, leave last one for ceremonial deletion
for (int i = 0; i < (int)toRemove.size()-1; i++) {
memset(toRemove[i], 0, sizeof(sItemBase));
}
}
void Database::getPlayer(Player* plr, int id) { void Database::getPlayer(Player* plr, int id) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
@ -1028,7 +1063,7 @@ void Database::getPlayer(Player* plr, int id) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
Database::removeExpiredVehicles(plr); removeExpiredVehicles(plr);
// get quest inventory // get quest inventory
sql = R"( sql = R"(
@ -1404,40 +1439,6 @@ void Database::updatePlayer(Player *player) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} }
void Database::removeExpiredVehicles(Player* player) {
int32_t currentTime = getTimestamp();
// if there are expired vehicles in bank just remove them silently
for (int i = 0; i < ABANK_COUNT; i++) {
if (player->Bank[i].iType == 10 && player->Bank[i].iTimeLimit < currentTime && player->Bank[i].iTimeLimit != 0) {
memset(&player->Bank[i], 0, sizeof(sItemBase));
}
}
// we want to leave only 1 expired vehicle on player to delete it with the client packet
std::vector<sItemBase*> toRemove;
// equipped vehicle
if (player->Equip[8].iOpt > 0 && player->Equip[8].iTimeLimit < currentTime && player->Equip[8].iTimeLimit != 0) {
toRemove.push_back(&player->Equip[8]);
player->toRemoveVehicle.eIL = 0;
player->toRemoveVehicle.iSlotNum = 8;
}
// inventory
for (int i = 0; i < AINVEN_COUNT; i++) {
if (player->Inven[i].iType == 10 && player->Inven[i].iTimeLimit < currentTime && player->Inven[i].iTimeLimit != 0) {
toRemove.push_back(&player->Inven[i]);
player->toRemoveVehicle.eIL = 1;
player->toRemoveVehicle.iSlotNum = i;
}
}
// delete all but one vehicles, leave last one for ceremonial deletion
for (int i = 0; i < (int)toRemove.size()-1; i++) {
memset(toRemove[i], 0, sizeof(sItemBase));
}
}
// buddies // buddies
// returns num of buddies + blocked players // returns num of buddies + blocked players
int Database::getNumBuddies(Player* player) { int Database::getNumBuddies(Player* player) {
@ -1559,10 +1560,10 @@ int Database::getUnreadEmailCount(int playerID) {
return ret; return ret;
} }
std::vector<Database::EmailData> Database::getEmails(int playerID, int page) { std::vector<EmailData> Database::getEmails(int playerID, int page) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
std::vector<Database::EmailData> emails; std::vector<EmailData> emails;
const char* sql = R"( const char* sql = R"(
SELECT SELECT
@ -1581,7 +1582,7 @@ std::vector<Database::EmailData> Database::getEmails(int playerID, int page) {
int offset = 5 * page - 5; int offset = 5 * page - 5;
sqlite3_bind_int(stmt, 2, offset); sqlite3_bind_int(stmt, 2, offset);
while (sqlite3_step(stmt) == SQLITE_ROW) { while (sqlite3_step(stmt) == SQLITE_ROW) {
Database::EmailData toAdd; EmailData toAdd;
toAdd.PlayerId = playerID; toAdd.PlayerId = playerID;
toAdd.MsgIndex = sqlite3_column_int(stmt, 0); toAdd.MsgIndex = sqlite3_column_int(stmt, 0);
toAdd.ItemFlag = sqlite3_column_int(stmt, 1); toAdd.ItemFlag = sqlite3_column_int(stmt, 1);
@ -1602,7 +1603,7 @@ std::vector<Database::EmailData> Database::getEmails(int playerID, int page) {
return emails; return emails;
} }
Database::EmailData Database::getEmail(int playerID, int index) { EmailData Database::getEmail(int playerID, int index) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"( const char* sql = R"(
@ -1618,7 +1619,7 @@ Database::EmailData Database::getEmail(int playerID, int index) {
sqlite3_bind_int(stmt, 1, playerID); sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int(stmt, 2, index); sqlite3_bind_int(stmt, 2, index);
Database::EmailData result; EmailData result;
if (sqlite3_step(stmt) != SQLITE_ROW) { if (sqlite3_step(stmt) != SQLITE_ROW) {
std::cout << "[WARN] Database: Email not found!" << std::endl; std::cout << "[WARN] Database: Email not found!" << std::endl;
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
@ -1873,7 +1874,7 @@ bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) {
return true; return true;
} }
Database::RaceRanking Database::getTopRaceRanking(int epID, int playerID) { RaceRanking Database::getTopRaceRanking(int epID, int playerID) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
std::string sql(R"( std::string sql(R"(
SELECT SELECT
@ -1897,7 +1898,7 @@ Database::RaceRanking Database::getTopRaceRanking(int epID, int playerID) {
if(playerID > -1) if(playerID > -1)
sqlite3_bind_int(stmt, 2, playerID); sqlite3_bind_int(stmt, 2, playerID);
Database::RaceRanking ranking = {}; RaceRanking ranking = {};
if (sqlite3_step(stmt) != SQLITE_ROW) { if (sqlite3_step(stmt) != SQLITE_ROW) {
// this race hasn't been run before, so return a blank ranking // this race hasn't been run before, so return a blank ranking
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
@ -1917,7 +1918,7 @@ Database::RaceRanking Database::getTopRaceRanking(int epID, int playerID) {
return ranking; return ranking;
} }
void Database::postRaceRanking(Database::RaceRanking ranking) { void Database::postRaceRanking(RaceRanking ranking) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"( const char* sql = R"(

View File

@ -15,6 +15,7 @@ namespace Database {
time_t BannedUntil; time_t BannedUntil;
std::string BanReason; std::string BanReason;
}; };
struct EmailData { struct EmailData {
int PlayerId; int PlayerId;
int MsgIndex; int MsgIndex;
@ -29,6 +30,7 @@ namespace Database {
uint64_t SendTime; uint64_t SendTime;
uint64_t DeleteTime; uint64_t DeleteTime;
}; };
struct RaceRanking { struct RaceRanking {
int EPID; int EPID;
int PlayerID; int PlayerID;
@ -40,20 +42,15 @@ namespace Database {
void open(); void open();
void close(); void close();
void checkMetaTable();
void createMetaTable();
void createTables();
int getTableSize(std::string tableName);
void findAccount(Account* account, std::string login); void findAccount(Account* account, std::string login);
/// returns ID, 0 if something failed // returns ID, 0 if something failed
int addAccount(std::string login, std::string password); int addAccount(std::string login, std::string password);
bool banAccount(int accountId, int days, std::string& reason);
bool unbanAccount(int accountId); // interface for the /ban command
bool banPlayer(int playerId, std::string& reason); bool banPlayer(int playerId, std::string& reason);
bool unbanPlayer(int playerId); bool unbanPlayer(int playerId);
int getAccountIDFromPlayerID(int playerId, int *accountLevel=nullptr);
void updateSelected(int accountId, int playerId); void updateSelected(int accountId, int playerId);
bool validateCharacter(int characterID, int userID); bool validateCharacter(int characterID, int userID);
@ -68,6 +65,7 @@ namespace Database {
/// returns slot number if query succeeded /// returns slot number if query succeeded
int deleteCharacter(int characterID, int userID); int deleteCharacter(int characterID, int userID);
void getCharInfo(std::vector <sP_LS2CL_REP_CHAR_INFO>* result, int userID); void getCharInfo(std::vector <sP_LS2CL_REP_CHAR_INFO>* result, int userID);
/// accepting/declining custom name /// accepting/declining custom name
enum class CustomName { enum class CustomName {
APPROVE = 1, APPROVE = 1,
@ -80,7 +78,6 @@ namespace Database {
// getting players // getting players
void getPlayer(Player* plr, int id); void getPlayer(Player* plr, int id);
void updatePlayer(Player *player); void updatePlayer(Player *player);
void removeExpiredVehicles(Player* player);
// buddies // buddies
int getNumBuddies(Player* player); int getNumBuddies(Player* player);