From 1f402aba95368da6627cccf49aef449a7fc90153 Mon Sep 17 00:00:00 2001 From: Loic Blot Date: Wed, 29 Mar 2017 00:28:14 +0200 Subject: [PATCH] WIP: player data to Database Add player data into databases (SQLite3 & PG only) This is a WIP ATM data is saved in Sqlite3 db and in file together, no reading is done No pg write atm --- src/client.cpp | 2 +- src/database-dummy.h | 3 +- src/database-postgresql.cpp | 107 +++++++++------- src/database-postgresql.h | 6 +- src/database-sqlite3.cpp | 249 +++++++++++++++++++++++++++++++++--- src/database-sqlite3.h | 65 +++++++++- src/database.h | 15 +++ src/map.cpp | 9 +- src/map.h | 2 - src/serverenvironment.cpp | 41 +++++- src/serverenvironment.h | 5 + 11 files changed, 426 insertions(+), 78 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 8bbaa83bd51bc..b387b0b27cfe1 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -913,7 +913,7 @@ void Client::initLocalMapSaving(const Address &address, fs::CreateAllDirs(world_path); - m_localdb = new Database_SQLite3(world_path); + m_localdb = new Database_SQLite3(world_path, DATABASE_TYPE_MAP); m_localdb->beginSave(); actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl; } diff --git a/src/database-dummy.h b/src/database-dummy.h index 72100edd0bdcb..9d0a4fee90eb3 100644 --- a/src/database-dummy.h +++ b/src/database-dummy.h @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database.h" #include "irrlichttypes.h" -class Database_Dummy : public Database +class Database_Dummy : public Database, public PlayerDatabase { public: bool saveBlock(const v3s16 &pos, const std::string &data); @@ -33,6 +33,7 @@ class Database_Dummy : public Database bool deleteBlock(const v3s16 &pos); void listAllLoadableBlocks(std::vector &dst); + bool savePlayer(RemotePlayer *player) { return true; } private: std::map m_database; }; diff --git a/src/database-postgresql.cpp b/src/database-postgresql.cpp index 83678fd52d8ac..e2d9b5c2d4322 100644 --- a/src/database-postgresql.cpp +++ b/src/database-postgresql.cpp @@ -40,12 +40,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "exceptions.h" #include "settings.h" -Database_PostgreSQL::Database_PostgreSQL(const Settings &conf) : - m_connect_string(""), +Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string, DatabaseType type) : + m_connect_string(connect_string), + m_db_type(type), m_conn(NULL), m_pgversion(0) { - if (!conf.getNoEx("pgsql_connection", m_connect_string)) { + if (m_connect_string.empty()) { throw SettingNotFoundException( "Set pgsql_connection string in world.mt to " "use the postgresql backend\n" @@ -120,36 +121,41 @@ bool Database_PostgreSQL::initialized() const void Database_PostgreSQL::initStatements() { - prepareStatement("read_block", + if (m_db_type == DATABASE_TYPE_MAP) { + prepareStatement("read_block", "SELECT data FROM blocks " - "WHERE posX = $1::int4 AND posY = $2::int4 AND " - "posZ = $3::int4"); - - if (m_pgversion < 90500) { - prepareStatement("write_block_insert", - "INSERT INTO blocks (posX, posY, posZ, data) SELECT " - "$1::int4, $2::int4, $3::int4, $4::bytea " - "WHERE NOT EXISTS (SELECT true FROM blocks " - "WHERE posX = $1::int4 AND posY = $2::int4 AND " - "posZ = $3::int4)"); - - prepareStatement("write_block_update", - "UPDATE blocks SET data = $4::bytea " - "WHERE posX = $1::int4 AND posY = $2::int4 AND " - "posZ = $3::int4"); - } else { - prepareStatement("write_block", - "INSERT INTO blocks (posX, posY, posZ, data) VALUES " - "($1::int4, $2::int4, $3::int4, $4::bytea) " - "ON CONFLICT ON CONSTRAINT blocks_pkey DO " - "UPDATE SET data = $4::bytea"); - } - - prepareStatement("delete_block", "DELETE FROM blocks WHERE " + "WHERE posX = $1::int4 AND posY = $2::int4 AND " + "posZ = $3::int4"); + + if (m_pgversion < 90500) { + prepareStatement("write_block_insert", + "INSERT INTO blocks (posX, posY, posZ, data) SELECT " + "$1::int4, $2::int4, $3::int4, $4::bytea " + "WHERE NOT EXISTS (SELECT true FROM blocks " + "WHERE posX = $1::int4 AND posY = $2::int4 AND " + "posZ = $3::int4)"); + + prepareStatement("write_block_update", + "UPDATE blocks SET data = $4::bytea " + "WHERE posX = $1::int4 AND posY = $2::int4 AND " + "posZ = $3::int4"); + } else { + prepareStatement("write_block", + "INSERT INTO blocks (posX, posY, posZ, data) VALUES " + "($1::int4, $2::int4, $3::int4, $4::bytea) " + "ON CONFLICT ON CONSTRAINT blocks_pkey DO " + "UPDATE SET data = $4::bytea"); + } + + prepareStatement("delete_block", "DELETE FROM blocks WHERE " "posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4"); - prepareStatement("list_all_loadable_blocks", + prepareStatement("list_all_loadable_blocks", "SELECT posX, posY, posZ FROM blocks"); + } + else if (m_db_type == DATABASE_TYPE_PLAYERS) { + // @TODO + } } PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear) @@ -175,25 +181,30 @@ PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear) void Database_PostgreSQL::createDatabase() { - PGresult *result = checkResults(PQexec(m_conn, - "SELECT relname FROM pg_class WHERE relname='blocks';"), - false); - - // If table doesn't exist, create it - if (!PQntuples(result)) { - static const char* dbcreate_sql = "CREATE TABLE blocks (" - "posX INT NOT NULL," - "posY INT NOT NULL," - "posZ INT NOT NULL," - "data BYTEA," - "PRIMARY KEY (posX,posY,posZ)" - ");"; - checkResults(PQexec(m_conn, dbcreate_sql)); - } + if (m_db_type == DATABASE_TYPE_MAP) { + PGresult *result = checkResults(PQexec(m_conn, + "SELECT relname FROM pg_class WHERE relname='blocks';"), + false); + + // If table doesn't exist, create it + if (!PQntuples(result)) { + static const char *dbcreate_sql = "CREATE TABLE blocks (" + "posX INT NOT NULL," + "posY INT NOT NULL," + "posZ INT NOT NULL," + "data BYTEA," + "PRIMARY KEY (posX,posY,posZ)" + ");"; + checkResults(PQexec(m_conn, dbcreate_sql)); + } - PQclear(result); + PQclear(result); - infostream << "PostgreSQL: Game Database was inited." << std::endl; + infostream << "PostgreSQL: Game Database was inited." << std::endl; + } + else if (m_db_type == DATABASE_TYPE_PLAYERS) { + // @TODO + } } @@ -302,4 +313,8 @@ void Database_PostgreSQL::listAllLoadableBlocks(std::vector &dst) PQclear(results); } +bool Database_PostgreSQL::savePlayer(RemotePlayer *player) +{ + return false; +} #endif // USE_POSTGRESQL diff --git a/src/database-postgresql.h b/src/database-postgresql.h index 1cfa544e33a8c..2ac0fae61f2de 100644 --- a/src/database-postgresql.h +++ b/src/database-postgresql.h @@ -27,10 +27,10 @@ with this program; if not, write to the Free Software Foundation, Inc., class Settings; -class Database_PostgreSQL : public Database +class Database_PostgreSQL: public Database, public PlayerDatabase { public: - Database_PostgreSQL(const Settings &conf); + Database_PostgreSQL(const std::string &connect_string, DatabaseType type); ~Database_PostgreSQL(); void beginSave(); @@ -42,6 +42,7 @@ class Database_PostgreSQL : public Database void listAllLoadableBlocks(std::vector &dst); bool initialized() const; + bool savePlayer(RemotePlayer *player); private: // Database initialization void connectToDatabase(); @@ -87,6 +88,7 @@ class Database_PostgreSQL : public Database // Attributes std::string m_connect_string; + DatabaseType m_db_type; PGconn *m_conn; int m_pgversion; }; diff --git a/src/database-sqlite3.cpp b/src/database-sqlite3.cpp index 095d485c0f725..30265fe9dbfee 100644 --- a/src/database-sqlite3.cpp +++ b/src/database-sqlite3.cpp @@ -33,6 +33,8 @@ SQLite format specification: #include "settings.h" #include "porting.h" #include "util/string.h" +#include "content_sao.h" +#include "remoteplayer.h" #include @@ -111,9 +113,10 @@ int Database_SQLite3::busyHandler(void *data, int count) } -Database_SQLite3::Database_SQLite3(const std::string &savedir) : +Database_SQLite3::Database_SQLite3(const std::string &savedir, DatabaseType type) : m_initialized(false), m_savedir(savedir), + m_db_type(type), m_database(NULL), m_stmt_read(NULL), m_stmt_write(NULL), @@ -138,11 +141,16 @@ void Database_SQLite3::endSave() { sqlite3_reset(m_stmt_end); } +static const std::string maptype_filename[] = { + "map", + "players" +}; + void Database_SQLite3::openDatabase() { if (m_database) return; - std::string dbp = m_savedir + DIR_DELIM + "map.sqlite"; + std::string dbp = m_savedir + DIR_DELIM + maptype_filename[m_db_type] + ".sqlite"; // Open the database connection @@ -180,18 +188,50 @@ void Database_SQLite3::verifyDatabase() PREPARE_STATEMENT(begin, "BEGIN"); PREPARE_STATEMENT(end, "COMMIT"); - PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1"); + + if (m_db_type == DATABASE_TYPE_MAP) { + PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1"); #ifdef __ANDROID__ - PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); + PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); #else - PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); + PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); #endif - PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?"); - PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`"); + PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?"); + PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`"); + verbosestream << "ServerMap: SQLite3 database opened." << std::endl; + } + else if (m_db_type == DATABASE_TYPE_PLAYERS) { + PREPARE_STATEMENT(player_load, "SELECT `pitch`, `yaw`, `posX`, `posY`, `posZ`, `hp`, " + "`breath`" + "FROM `player` WHERE `name` = ?") + PREPARE_STATEMENT(player_add, "INSERT INTO `player` (`name`, `pitch`, `yaw`, `posX`, " + "`posY`, `posZ`, `hp`, `breath`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") + PREPARE_STATEMENT(player_update, "UPDATE `player` SET `pitch` = ?, `yaw` = ?, `posX` = ?, " + "`posY` = ?, `posZ` = ?, `hp` = ?, `breath` = ? WHERE `name` = ?") + + PREPARE_STATEMENT(player_add_inventory, "INSERT INTO `player_inventories` " + "(`player`, `inv_id`, `inv_width`, `inv_name`, `inv_size`) VALUES (?, ?, ?, ?, ?)") + PREPARE_STATEMENT(player_add_inventory_items, "INSERT INTO `player_inventory_items` " + "(`player`, `inv_id`, `slot_id`, `item`) VALUES (?, ?, ?, ?)") + PREPARE_STATEMENT(player_remove_inventory, "DELETE FROM `player_inventories` " + "WHERE `player` = ?") + PREPARE_STATEMENT(player_remove_inventory_items, "DELETE FROM `player_inventory_items` " + "WHERE `player` = ?") + PREPARE_STATEMENT(player_load_inventory, "SELECT `inv_id`, `inv_width`, `inv_name`, " + "`inv_size` FROM `player_inventories` WHERE `player` = ? ORDER BY inv_id") + PREPARE_STATEMENT(player_load_inventory_items, "SELECT `slot_id`, `item` " + "FROM `player_inventory_items` WHERE `player` = ? AND `inv_id` = ?") + + PREPARE_STATEMENT(player_metadata_load, "SELECT `metadata`, `value` FROM " + "`player_metadatas` WHERE `player` = ?") + PREPARE_STATEMENT(player_metadata_add, "INSERT INTO `player_metadatas` " + "(`player`, `metadata`, `value`) VALUES (?, ?, ?)") + PREPARE_STATEMENT(player_metadata_remove, "DELETE FROM `player_metadatas` " + "WHERE `player` = ?") + verbosestream << "ServerEnvironment: SQLite3 database opened (players)." << std::endl; + } m_initialized = true; - - verbosestream << "ServerMap: SQLite3 database opened." << std::endl; } inline void Database_SQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index) @@ -264,16 +304,169 @@ void Database_SQLite3::loadBlock(const v3s16 &pos, std::string *block) sqlite3_reset(m_stmt_read); } +bool Database_SQLite3::playerDataExists(const std::string &name) +{ + verifyDatabase(); + str_to_sqlite(m_stmt_player_load, 1, name); + bool res = (sqlite3_step(m_stmt_player_load) == SQLITE_ROW); + sqlite3_reset(m_stmt_player_load); + return res; +} + +bool Database_SQLite3::savePlayer(RemotePlayer *player) +{ + PlayerSAO* sao = player->getPlayerSAO(); + if (!sao) + return false; + + const v3f &pos = sao->getBasePosition(); + // Begin save in brace is mandatory + if (!playerDataExists(player->getName())) { + beginSave(); + str_to_sqlite(m_stmt_player_add, 1, player->getName()); + double_to_sqlite(m_stmt_player_add, 2, sao->getPitch()); + double_to_sqlite(m_stmt_player_add, 3, sao->getYaw()); + double_to_sqlite(m_stmt_player_add, 4, pos.X); + double_to_sqlite(m_stmt_player_add, 5, pos.Y); + double_to_sqlite(m_stmt_player_add, 6, pos.Z); + int64_to_sqlite(m_stmt_player_add, 7, sao->getHP()); + int64_to_sqlite(m_stmt_player_add, 8, sao->getBreath()); + + sqlite3_vrfy(sqlite3_step(m_stmt_player_add), SQLITE_DONE); + sqlite3_reset(m_stmt_player_add); + } + else { + beginSave(); + double_to_sqlite(m_stmt_player_update, 1, sao->getPitch()); + double_to_sqlite(m_stmt_player_update, 2, sao->getYaw()); + double_to_sqlite(m_stmt_player_update, 3, pos.X); + double_to_sqlite(m_stmt_player_update, 4, pos.Y); + double_to_sqlite(m_stmt_player_update, 5, pos.Z); + sqlite3_vrfy(sqlite3_bind_int64(m_stmt_player_update, 6, sao->getHP())); + sqlite3_vrfy(sqlite3_bind_int64(m_stmt_player_update, 7, sao->getBreath())); + str_to_sqlite(m_stmt_player_update, 8, player->getName()); + + sqlite3_vrfy(sqlite3_step(m_stmt_player_update), SQLITE_DONE); + sqlite3_reset(m_stmt_player_update); + } + + // Write player inventories + str_to_sqlite(m_stmt_player_remove_inventory, 1, player->getName()); + sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory), SQLITE_DONE); + sqlite3_reset(m_stmt_player_remove_inventory); + + str_to_sqlite(m_stmt_player_remove_inventory_items, 1, player->getName()); + sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory_items), SQLITE_DONE); + sqlite3_reset(m_stmt_player_remove_inventory_items); + + + std::vector inventory_lists = sao->getInventory()->getLists(); + for (u16 i = 0; i < inventory_lists.size(); i++) { + const InventoryList* list = inventory_lists[i]; + + str_to_sqlite(m_stmt_player_add_inventory, 1, player->getName()); + int_to_sqlite(m_stmt_player_add_inventory, 2, i); + int_to_sqlite(m_stmt_player_add_inventory, 3, list->getWidth()); + str_to_sqlite(m_stmt_player_add_inventory, 4, list->getName()); + int_to_sqlite(m_stmt_player_add_inventory, 5, list->getSize()); + sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory), SQLITE_DONE); + sqlite3_reset(m_stmt_player_add_inventory); + + for (u32 j = 0; j < list->getSize(); j++) { + std::ostringstream os; + list->getItem(j).serialize(os); + std::string itemStr = os.str(); + + str_to_sqlite(m_stmt_player_add_inventory_items, 1, player->getName()); + int_to_sqlite(m_stmt_player_add_inventory_items, 2, i); + int_to_sqlite(m_stmt_player_add_inventory_items, 3, j); + str_to_sqlite(m_stmt_player_add_inventory_items, 4, itemStr); + sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory_items), SQLITE_DONE); + sqlite3_reset(m_stmt_player_add_inventory_items); + } + } + + // TODO meta required too + str_to_sqlite(m_stmt_player_metadata_remove, 1, player->getName()); + sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_remove), SQLITE_DONE); + sqlite3_reset(m_stmt_player_metadata_remove); + + const PlayerAttributes &attrs = sao->getExtendedAttributes(); + for (PlayerAttributes::const_iterator it = attrs.begin(); it != attrs.end(); ++it) { + str_to_sqlite(m_stmt_player_metadata_add, 1, player->getName()); + str_to_sqlite(m_stmt_player_metadata_add, 2, it->first); + str_to_sqlite(m_stmt_player_metadata_add, 3, it->second); + sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_add), SQLITE_DONE); + sqlite3_reset(m_stmt_player_metadata_add); + } + + + endSave(); + + return true; +} + void Database_SQLite3::createDatabase() { assert(m_database); // Pre-condition - SQLOK(sqlite3_exec(m_database, - "CREATE TABLE IF NOT EXISTS `blocks` (\n" - " `pos` INT PRIMARY KEY,\n" - " `data` BLOB\n" - ");\n", + + if (m_db_type == DATABASE_TYPE_MAP) { + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `blocks` (\n" + " `pos` INT PRIMARY KEY,\n" + " `data` BLOB\n" + ");\n", + NULL, NULL, NULL), + "Failed to create database table"); + } + else if (m_db_type == DATABASE_TYPE_PLAYERS) { + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `player` (" + "`name` VARCHAR(50) NOT NULL," + "`pitch` NUMERIC(11, 4) NOT NULL," + "`yaw` NUMERIC(11, 4) NOT NULL," + "`posX` NUMERIC(11, 4) NOT NULL," + "`posY` NUMERIC(11, 4) NOT NULL," + "`posZ` NUMERIC(11, 4) NOT NULL," + "`hp` INT NOT NULL," + "`breath` INT NOT NULL," + "PRIMARY KEY (`name`));", NULL, NULL, NULL), - "Failed to create database table"); + "Failed to create player table"); + + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `player_metadatas` (" + " `player` VARCHAR(50) NOT NULL," + " `metadata` VARCHAR(256) NOT NULL," + " `value` TEXT," + " PRIMARY KEY(`player`, `metadata`)," + " FOREIGN KEY (`player`) REFERENCES players (`name`) ON DELETE CASCADE );", + NULL, NULL, NULL), + "Failed to create player metadata table"); + + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `player_inventories` (" + " `player` VARCHAR(50) NOT NULL," + " `inv_id` INT NOT NULL," + " `inv_width` INT NOT NULL," + " `inv_name` TEXT NOT NULL DEFAULT ''," + " `inv_size` INT NOT NULL," + " PRIMARY KEY(player, inv_id)," + " FOREIGN KEY (`player`) REFERENCES players (`name`) ON DELETE CASCADE );", + NULL, NULL, NULL), + "Failed to create player inventory table"); + + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE `player_inventory_items` (" + " `player` VARCHAR(50) NOT NULL," + " `inv_id` INT NOT NULL," + " `slot_id` INT NOT NULL," + " `item` TEXT NOT NULL DEFAULT ''," + " PRIMARY KEY(player, inv_id, slot_id)," + " FOREIGN KEY (`player`) REFERENCES players (`name`) ON DELETE CASCADE );", + NULL, NULL, NULL), + "Failed to create player inventory items table"); + } } void Database_SQLite3::listAllLoadableBlocks(std::vector &dst) @@ -288,12 +481,30 @@ void Database_SQLite3::listAllLoadableBlocks(std::vector &dst) Database_SQLite3::~Database_SQLite3() { - FINALIZE_STATEMENT(m_stmt_read) - FINALIZE_STATEMENT(m_stmt_write) - FINALIZE_STATEMENT(m_stmt_list) + if (m_db_type == DATABASE_TYPE_MAP) { + FINALIZE_STATEMENT(m_stmt_read) + FINALIZE_STATEMENT(m_stmt_write) + FINALIZE_STATEMENT(m_stmt_list) + FINALIZE_STATEMENT(m_stmt_delete) + } + else if (m_db_type == DATABASE_TYPE_PLAYERS) { + FINALIZE_STATEMENT(m_stmt_player_load) + FINALIZE_STATEMENT(m_stmt_player_add) + FINALIZE_STATEMENT(m_stmt_player_update) + FINALIZE_STATEMENT(m_stmt_player_add_inventory) + FINALIZE_STATEMENT(m_stmt_player_add_inventory_items) + FINALIZE_STATEMENT(m_stmt_player_remove_inventory) + FINALIZE_STATEMENT(m_stmt_player_remove_inventory_items) + FINALIZE_STATEMENT(m_stmt_player_load_inventory) + FINALIZE_STATEMENT(m_stmt_player_load_inventory_items) + FINALIZE_STATEMENT(m_stmt_player_metadata_load) + FINALIZE_STATEMENT(m_stmt_player_metadata_add) + FINALIZE_STATEMENT(m_stmt_player_metadata_remove) + } + FINALIZE_STATEMENT(m_stmt_begin) FINALIZE_STATEMENT(m_stmt_end) - FINALIZE_STATEMENT(m_stmt_delete) + SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database"); } diff --git a/src/database-sqlite3.h b/src/database-sqlite3.h index debbc9d8bfb6d..7cfe26a4fd7af 100644 --- a/src/database-sqlite3.h +++ b/src/database-sqlite3.h @@ -21,16 +21,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #define DATABASE_SQLITE3_HEADER #include "database.h" +#include "exceptions.h" #include +#include extern "C" { #include "sqlite3.h" } -class Database_SQLite3 : public Database +class Database_SQLite3 : public Database, public PlayerDatabase { public: - Database_SQLite3(const std::string &savedir); + Database_SQLite3(const std::string &savedir, DatabaseType type); ~Database_SQLite3(); void beginSave(); @@ -42,6 +44,7 @@ class Database_SQLite3 : public Database void listAllLoadableBlocks(std::vector &dst); bool initialized() const { return m_initialized; } + bool savePlayer(RemotePlayer *player); private: // Open the database void openDatabase(); @@ -52,15 +55,73 @@ class Database_SQLite3 : public Database void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index=1); + bool playerDataExists(const std::string &name); + // Convertors + inline void str_to_sqlite(sqlite3_stmt *s, const int iCol, const std::string &str) const + { + sqlite3_vrfy(sqlite3_bind_text(s, iCol, str.c_str(), str.size(), NULL)); + } + + inline void str_to_sqlite(sqlite3_stmt *s, const int iCol, const char *str) const + { + sqlite3_vrfy(sqlite3_bind_text(s, iCol, str, strlen(str), NULL)); + } + + inline void int_to_sqlite(sqlite3_stmt *s, const int iCol, const int val) const + { + sqlite3_vrfy(sqlite3_bind_int(s, iCol, val)); + } + + inline void int64_to_sqlite(sqlite3_stmt *s, const int iCol, const s64 val) const + { + sqlite3_vrfy(sqlite3_bind_int64(s, iCol, (sqlite3_uint64)val)); + } + + inline void double_to_sqlite(sqlite3_stmt *s, const int iCol, const double val) const + { + sqlite3_vrfy(sqlite3_bind_double(s, iCol, val)); + } + + // Query verifiers helpers + inline void sqlite3_vrfy(const int s, const std::string &m = "", const int r = SQLITE_OK) const + { + if ((s) != (r)) { \ + throw DatabaseException(std::string(m) + ": " + sqlite3_errmsg(m_database)); + } + } + + inline void sqlite3_vrfy(const int s, const int r, const std::string &m = "") const + { + sqlite3_vrfy(s, m, r); + } + bool m_initialized; std::string m_savedir; + DatabaseType m_db_type; sqlite3 *m_database; + + // Map sqlite3_stmt *m_stmt_read; sqlite3_stmt *m_stmt_write; sqlite3_stmt *m_stmt_list; sqlite3_stmt *m_stmt_delete; + + // Players + sqlite3_stmt *m_stmt_player_load; + sqlite3_stmt *m_stmt_player_add; + sqlite3_stmt *m_stmt_player_update; + sqlite3_stmt *m_stmt_player_load_inventory; + sqlite3_stmt *m_stmt_player_load_inventory_items; + sqlite3_stmt *m_stmt_player_add_inventory; + sqlite3_stmt *m_stmt_player_add_inventory_items; + sqlite3_stmt *m_stmt_player_remove_inventory; + sqlite3_stmt *m_stmt_player_remove_inventory_items; + sqlite3_stmt *m_stmt_player_metadata_load; + sqlite3_stmt *m_stmt_player_metadata_remove; + sqlite3_stmt *m_stmt_player_metadata_add; + sqlite3_stmt *m_stmt_begin; sqlite3_stmt *m_stmt_end; diff --git a/src/database.h b/src/database.h index 4597893069101..9004d43edae9e 100644 --- a/src/database.h +++ b/src/database.h @@ -26,6 +26,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include "util/basic_macros.h" +enum DatabaseType +{ + DATABASE_TYPE_MAP, + DATABASE_TYPE_PLAYERS, +}; + class Database { public: @@ -46,5 +52,14 @@ class Database virtual bool initialized() const { return true; } }; +class RemotePlayer; + +class PlayerDatabase +{ +public: + virtual ~PlayerDatabase() {} + virtual bool savePlayer(RemotePlayer *player) = 0; +}; + #endif diff --git a/src/map.cpp b/src/map.cpp index b690ea3b3c606..c0d4e5e88c556 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2281,7 +2281,7 @@ Database *ServerMap::createDatabase( Settings &conf) { if (name == "sqlite3") - return new Database_SQLite3(savedir); + return new Database_SQLite3(savedir, DATABASE_TYPE_MAP); if (name == "dummy") return new Database_Dummy(); #if USE_LEVELDB @@ -2293,8 +2293,11 @@ Database *ServerMap::createDatabase( return new Database_Redis(conf); #endif #if USE_POSTGRESQL - else if (name == "postgresql") - return new Database_PostgreSQL(conf); + else if (name == "postgresql") { + std::string connect_string = ""; + conf.getNoEx("pgsql_connection", connect_string); + return new Database_PostgreSQL(connect_string, DATABASE_TYPE_MAP); + } #endif else throw BaseException(std::string("Database backend ") + name + " not supported."); diff --git a/src/map.h b/src/map.h index ea8dc76d1ba9a..29dbfcf0e58a0 100644 --- a/src/map.h +++ b/src/map.h @@ -430,8 +430,6 @@ class ServerMap : public Map Database functions */ static Database *createDatabase(const std::string &name, const std::string &savedir, Settings &conf); - // Verify we can read/write to the database - void verifyDatabase(); // Returns true if the database file does not exist bool loadFromFolders(); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index e09c7da16b443..1cf0b7c0cad62 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -36,6 +36,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/pointedthing.h" #include "threading/mutex_auto_lock.h" #include "filesys.h" +#include "database-postgresql.h" +#include "database-dummy.h" +#include "database-sqlite3.h" #define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:" @@ -365,8 +368,10 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, m_game_time_fraction_counter(0), m_last_clear_objects_time(0), m_recommended_send_interval(0.1), - m_max_lag_estimate(0.1) + m_max_lag_estimate(0.1), + m_player_database(NULL) { + openPlayerDatabase(path_world); } ServerEnvironment::~ServerEnvironment() @@ -392,6 +397,8 @@ ServerEnvironment::~ServerEnvironment() i != m_players.end(); ++i) { delete (*i); } + + delete m_player_database; } Map & ServerEnvironment::getMap() @@ -495,7 +502,7 @@ void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason, void ServerEnvironment::saveLoadedPlayers() { - std::string players_path = m_path_world + DIR_DELIM "players"; + std::string players_path = m_path_world + DIR_DELIM + "players"; fs::CreateDir(players_path); for (std::vector::iterator it = m_players.begin(); @@ -504,6 +511,7 @@ void ServerEnvironment::saveLoadedPlayers() if ((*it)->checkModified() || ((*it)->getPlayerSAO() && (*it)->getPlayerSAO()->extendedAttributesModified())) { (*it)->save(players_path, m_server); + m_player_database->savePlayer(*it); } } } @@ -514,6 +522,7 @@ void ServerEnvironment::savePlayer(RemotePlayer *player) fs::CreateDir(players_path); player->save(players_path, m_server); + m_player_database->savePlayer(player); } RemotePlayer *ServerEnvironment::loadPlayer(const std::string &playername, PlayerSAO *sao) @@ -2173,3 +2182,31 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete) m_active_objects.erase(*i); } } + +void ServerEnvironment::openPlayerDatabase(const std::string &savedir) +{ + // Determine which database backend to use + std::string conf_path = savedir + DIR_DELIM + "world.mt"; + Settings conf; + bool succeeded = conf.readConfigFile(conf_path.c_str()); + if (!succeeded || !conf.exists("player_backend")) { + // fall back to sqlite3 + conf.set("player_backend", "sqlite3"); + } + + std::string name = ""; + conf.getNoEx("player_backend", name); + if (name == "sqlite3") + m_player_database = new Database_SQLite3(savedir, DATABASE_TYPE_PLAYERS); + else if (name == "dummy") + m_player_database = new Database_Dummy(); +#if USE_POSTGRESQL + else if (name == "postgresql") { + std::string connect_string = ""; + conf.getNoEx("pgsql_player_connection", connect_string); + m_player_database = new Database_PostgreSQL(connect_string, DATABASE_TYPE_PLAYERS); + } +#endif + else + throw BaseException(std::string("Database backend ") + name + " not supported."); +} diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 99110542a5c91..97989abc1d764 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class IGameDef; class ServerMap; class RemotePlayer; +class PlayerDatabase; class PlayerSAO; class ServerEnvironment; class ActiveBlockModifier; @@ -374,6 +375,8 @@ class ServerEnvironment : public Environment */ void deactivateFarObjects(bool force_delete); + void openPlayerDatabase(const std::string &savedir); + /* Member variables */ @@ -419,6 +422,8 @@ class ServerEnvironment : public Environment // peer_ids in here should be unique, except that there may be many 0s std::vector m_players; + PlayerDatabase *m_player_database; + // Particles IntervalLimiter m_particle_management_interval; UNORDERED_MAP m_particle_spawners;