aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoïc Blot <nerzhul@users.noreply.github.com>2017-04-23 14:35:08 +0200
committerGitHub <noreply@github.com>2017-04-23 14:35:08 +0200
commit29ab20c27229672c24a7699afbcd54caad903331 (patch)
treee42e7ea35f54b1439055abc500191eb29baa6355
parentdda171d2925e20efc00c78bcb45cf595fd986da9 (diff)
downloadminetest-29ab20c27229672c24a7699afbcd54caad903331.tar.gz
minetest-29ab20c27229672c24a7699afbcd54caad903331.tar.bz2
minetest-29ab20c27229672c24a7699afbcd54caad903331.zip
Player data to Database (#5475)
* Player data to Database Add player data into databases (SQLite3 & PG only) PostgreSQL & SQLite: better POO Design for databases Add --migrate-players argument to server + deprecation warning * Remove players directory if empty
-rw-r--r--build/android/jni/Android.mk1
-rw-r--r--builtin/game/chatcommands.lua25
-rw-r--r--doc/lua_api.txt2
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/client.cpp2
-rw-r--r--src/client.h4
-rw-r--r--src/content_sao.cpp7
-rw-r--r--src/content_sao.h4
-rw-r--r--src/database-dummy.h9
-rw-r--r--src/database-files.cpp179
-rw-r--r--src/database-files.h46
-rw-r--r--src/database-leveldb.h4
-rw-r--r--src/database-postgresql.cpp470
-rw-r--r--src/database-postgresql.h115
-rw-r--r--src/database-redis.h2
-rw-r--r--src/database-sqlite3.cpp399
-rw-r--r--src/database-sqlite3.h158
-rw-r--r--src/database.cpp4
-rw-r--r--src/database.h24
-rw-r--r--src/main.cpp21
-rw-r--r--src/map.cpp13
-rw-r--r--src/map.h8
-rw-r--r--src/remoteplayer.cpp48
-rw-r--r--src/remoteplayer.h2
-rw-r--r--src/script/lua_api/l_server.cpp17
-rw-r--r--src/script/lua_api/l_server.h3
-rw-r--r--src/server.cpp50
-rw-r--r--src/server.h3
-rw-r--r--src/serverenvironment.cpp234
-rw-r--r--src/serverenvironment.h13
-rw-r--r--src/unittest/test_player.cpp49
31 files changed, 1547 insertions, 370 deletions
diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk
index 2929eaba1..b652c6b5e 100644
--- a/build/android/jni/Android.mk
+++ b/build/android/jni/Android.mk
@@ -134,6 +134,7 @@ LOCAL_SRC_FILES := \
jni/src/convert_json.cpp \
jni/src/craftdef.cpp \
jni/src/database-dummy.cpp \
+ jni/src/database-files.cpp \
jni/src/database-sqlite3.cpp \
jni/src/database.cpp \
jni/src/debug.cpp \
diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua
index 84f2c3fed..cbf75c1bc 100644
--- a/builtin/game/chatcommands.lua
+++ b/builtin/game/chatcommands.lua
@@ -279,6 +279,31 @@ core.register_chatcommand("auth_reload", {
end,
})
+core.register_chatcommand("remove_player", {
+ params = "<name>",
+ description = "Remove player data",
+ privs = {server=true},
+ func = function(name, param)
+ local toname = param
+ if toname == "" then
+ return false, "Name field required"
+ end
+
+ local rc = core.remove_player(toname)
+
+ if rc == 0 then
+ core.log("action", name .. " removed player data of " .. toname .. ".")
+ return true, "Player \"" .. toname .. "\" removed."
+ elseif rc == 1 then
+ return true, "No such player \"" .. toname .. "\" to remove."
+ elseif rc == 2 then
+ return true, "Player \"" .. toname .. "\" is connected, cannot remove."
+ end
+
+ return false, "Unhandled remove_player return code " .. rc .. ""
+ end,
+})
+
core.register_chatcommand("teleport", {
params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>",
description = "Teleport to player or position",
diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index 33254fb2a..c3d8d2bf6 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -2599,6 +2599,8 @@ These functions return the leftover itemstack.
* `minetest.cancel_shutdown_requests()`: cancel current delayed shutdown
* `minetest.get_server_status()`: returns server status string
* `minetest.get_server_uptime()`: returns the server uptime in seconds
+* `minetest.remove_player(name)`: remove player from database (if he is not connected).
+ * Returns a code (0: successful, 1: no such player, 2: player is connected)
### Bans
* `minetest.get_ban_list()`: returns the ban list (same as `minetest.get_ban_description("")`)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 37f72a44d..7f779db10 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -377,6 +377,7 @@ set(common_SRCS
convert_json.cpp
craftdef.cpp
database-dummy.cpp
+ database-files.cpp
database-leveldb.cpp
database-postgresql.cpp
database-redis.cpp
diff --git a/src/client.cpp b/src/client.cpp
index ce42d025e..94c808a57 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -770,7 +770,7 @@ void Client::initLocalMapSaving(const Address &address,
fs::CreateAllDirs(world_path);
- m_localdb = new Database_SQLite3(world_path);
+ m_localdb = new MapDatabaseSQLite3(world_path);
m_localdb->beginSave();
actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl;
}
diff --git a/src/client.h b/src/client.h
index 5dc3f9bc8..328a24f90 100644
--- a/src/client.h
+++ b/src/client.h
@@ -49,7 +49,7 @@ class ClientMediaDownloader;
struct MapDrawControl;
class MtEventManager;
struct PointedThing;
-class Database;
+class MapDatabase;
class Minimap;
struct MinimapMapblock;
class Camera;
@@ -645,7 +645,7 @@ private:
LocalClientState m_state;
// Used for saving server map to disk client-side
- Database *m_localdb;
+ MapDatabase *m_localdb;
IntervalLimiter m_localdb_save_interval;
u16 m_cache_save_interval;
diff --git a/src/content_sao.cpp b/src/content_sao.cpp
index 355453fc9..caf6dcbab 100644
--- a/src/content_sao.cpp
+++ b/src/content_sao.cpp
@@ -764,9 +764,10 @@ bool LuaEntitySAO::collideWithObjects() const
// No prototype, PlayerSAO does not need to be deserialized
-PlayerSAO::PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer):
+PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id_,
+ bool is_singleplayer):
UnitSAO(env_, v3f(0,0,0)),
- m_player(NULL),
+ m_player(player_),
m_peer_id(peer_id_),
m_inventory(NULL),
m_damage(0),
@@ -819,7 +820,7 @@ PlayerSAO::~PlayerSAO()
delete m_inventory;
}
-void PlayerSAO::initialize(RemotePlayer *player, const std::set<std::string> &privs)
+void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
{
assert(player);
m_player = player;
diff --git a/src/content_sao.h b/src/content_sao.h
index e53e8ecce..e08795579 100644
--- a/src/content_sao.h
+++ b/src/content_sao.h
@@ -194,7 +194,7 @@ class RemotePlayer;
class PlayerSAO : public UnitSAO
{
public:
- PlayerSAO(ServerEnvironment *env_, u16 peer_id_, bool is_singleplayer);
+ PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id_, bool is_singleplayer);
~PlayerSAO();
ActiveObjectType getType() const
{ return ACTIVEOBJECT_TYPE_PLAYER; }
@@ -349,7 +349,7 @@ public:
bool getCollisionBox(aabb3f *toset) const;
bool collideWithObjects() const { return true; }
- void initialize(RemotePlayer *player, const std::set<std::string> &privs);
+ void finalize(RemotePlayer *player, const std::set<std::string> &privs);
v3f getEyePosition() const { return m_base_position + getEyeOffset(); }
v3f getEyeOffset() const;
diff --git a/src/database-dummy.h b/src/database-dummy.h
index 9083850cb..7d1cb2279 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 MapDatabase, public PlayerDatabase
{
public:
bool saveBlock(const v3s16 &pos, const std::string &data);
@@ -33,6 +33,13 @@ public:
bool deleteBlock(const v3s16 &pos);
void listAllLoadableBlocks(std::vector<v3s16> &dst);
+ void savePlayer(RemotePlayer *player) {}
+ bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) { return true; }
+ bool removePlayer(const std::string &name) { return true; }
+ void listPlayers(std::vector<std::string> &) {}
+
+ void beginSave() {}
+ void endSave() {}
private:
std::map<s64, std::string> m_database;
};
diff --git a/src/database-files.cpp b/src/database-files.cpp
new file mode 100644
index 000000000..08a1f2d03
--- /dev/null
+++ b/src/database-files.cpp
@@ -0,0 +1,179 @@
+/*
+Minetest
+Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <cassert>
+#include <json/json.h>
+#include "database-files.h"
+#include "content_sao.h"
+#include "remoteplayer.h"
+#include "settings.h"
+#include "porting.h"
+#include "filesys.h"
+
+// !!! WARNING !!!
+// This backend is intended to be used on Minetest 0.4.16 only for the transition backend for
+// player files
+
+void PlayerDatabaseFiles::serialize(std::ostringstream &os, RemotePlayer *player)
+{
+ // Utilize a Settings object for storing values
+ Settings args;
+ args.setS32("version", 1);
+ args.set("name", player->getName());
+
+ sanity_check(player->getPlayerSAO());
+ args.setS32("hp", player->getPlayerSAO()->getHP());
+ args.setV3F("position", player->getPlayerSAO()->getBasePosition());
+ args.setFloat("pitch", player->getPlayerSAO()->getPitch());
+ args.setFloat("yaw", player->getPlayerSAO()->getYaw());
+ args.setS32("breath", player->getPlayerSAO()->getBreath());
+
+ std::string extended_attrs = "";
+ player->serializeExtraAttributes(extended_attrs);
+ args.set("extended_attributes", extended_attrs);
+
+ args.writeLines(os);
+
+ os << "PlayerArgsEnd\n";
+
+ player->inventory.serialize(os);
+}
+
+void PlayerDatabaseFiles::savePlayer(RemotePlayer *player)
+{
+ std::string savedir = m_savedir + DIR_DELIM;
+ std::string path = savedir + player->getName();
+ bool path_found = false;
+ RemotePlayer testplayer("", NULL);
+
+ for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES && !path_found; i++) {
+ if (!fs::PathExists(path)) {
+ path_found = true;
+ continue;
+ }
+
+ // Open and deserialize file to check player name
+ std::ifstream is(path.c_str(), std::ios_base::binary);
+ if (!is.good()) {
+ errorstream << "Failed to open " << path << std::endl;
+ return;
+ }
+
+ testplayer.deSerialize(is, path, NULL);
+ is.close();
+ if (strcmp(testplayer.getName(), player->getName()) == 0) {
+ path_found = true;
+ continue;
+ }
+
+ path = savedir + player->getName() + itos(i);
+ }
+
+ if (!path_found) {
+ errorstream << "Didn't find free file for player " << player->getName()
+ << std::endl;
+ return;
+ }
+
+ // Open and serialize file
+ std::ostringstream ss(std::ios_base::binary);
+ serialize(ss, player);
+ if (!fs::safeWriteToFile(path, ss.str())) {
+ infostream << "Failed to write " << path << std::endl;
+ }
+ player->setModified(false);
+}
+
+bool PlayerDatabaseFiles::removePlayer(const std::string &name)
+{
+ std::string players_path = m_savedir + DIR_DELIM;
+ std::string path = players_path + name;
+
+ RemotePlayer temp_player("", NULL);
+ for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
+ // Open file and deserialize
+ std::ifstream is(path.c_str(), std::ios_base::binary);
+ if (!is.good())
+ continue;
+
+ temp_player.deSerialize(is, path, NULL);
+ is.close();
+
+ if (temp_player.getName() == name) {
+ fs::DeleteSingleFileOrEmptyDirectory(path);
+ return true;
+ }
+
+ path = players_path + name + itos(i);
+ }
+
+ return false;
+}
+
+bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
+{
+ std::string players_path = m_savedir + DIR_DELIM;
+ std::string path = players_path + player->getName();
+
+ const std::string player_to_load = player->getName();
+ for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
+ // Open file and deserialize
+ std::ifstream is(path.c_str(), std::ios_base::binary);
+ if (!is.good())
+ continue;
+
+ player->deSerialize(is, path, sao);
+ is.close();
+
+ if (player->getName() == player_to_load)
+ return true;
+
+ path = players_path + player_to_load + itos(i);
+ }
+
+ infostream << "Player file for player " << player_to_load << " not found" << std::endl;
+ return false;
+}
+
+void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
+{
+ std::vector<fs::DirListNode> files = fs::GetDirListing(m_savedir);
+ // list files into players directory
+ for (std::vector<fs::DirListNode>::const_iterator it = files.begin(); it !=
+ files.end(); ++it) {
+ // Ignore directories
+ if (it->dir)
+ continue;
+
+ const std::string &filename = it->name;
+ std::string full_path = m_savedir + DIR_DELIM + filename;
+ std::ifstream is(full_path.c_str(), std::ios_base::binary);
+ if (!is.good())
+ continue;
+
+ RemotePlayer player(filename.c_str(), NULL);
+ // Null env & dummy peer_id
+ PlayerSAO playerSAO(NULL, &player, 15789, false);
+
+ player.deSerialize(is, "", &playerSAO);
+ is.close();
+
+ res.push_back(player.getName());
+ }
+}
diff --git a/src/database-files.h b/src/database-files.h
new file mode 100644
index 000000000..d23069c2a
--- /dev/null
+++ b/src/database-files.h
@@ -0,0 +1,46 @@
+/*
+Minetest
+Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef DATABASE_FILES_HEADER
+#define DATABASE_FILES_HEADER
+
+// !!! WARNING !!!
+// This backend is intended to be used on Minetest 0.4.16 only for the transition backend for
+// player files
+
+#include "database.h"
+
+class PlayerDatabaseFiles : public PlayerDatabase
+{
+public:
+ PlayerDatabaseFiles(const std::string &savedir) : m_savedir(savedir) {}
+ virtual ~PlayerDatabaseFiles() {}
+
+ void savePlayer(RemotePlayer *player);
+ bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
+ bool removePlayer(const std::string &name);
+ void listPlayers(std::vector<std::string> &res);
+
+private:
+ void serialize(std::ostringstream &os, RemotePlayer *player);
+
+ std::string m_savedir;
+};
+
+#endif
diff --git a/src/database-leveldb.h b/src/database-leveldb.h
index 171946741..52ccebe70 100644
--- a/src/database-leveldb.h
+++ b/src/database-leveldb.h
@@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "database.h"
#include "leveldb/db.h"
-class Database_LevelDB : public Database
+class Database_LevelDB : public MapDatabase
{
public:
Database_LevelDB(const std::string &savedir);
@@ -39,6 +39,8 @@ public:
bool deleteBlock(const v3s16 &pos);
void listAllLoadableBlocks(std::vector<v3s16> &dst);
+ void beginSave() {}
+ void endSave() {}
private:
leveldb::DB *m_database;
};
diff --git a/src/database-postgresql.cpp b/src/database-postgresql.cpp
index 83678fd52..a6b62bad5 100644
--- a/src/database-postgresql.cpp
+++ b/src/database-postgresql.cpp
@@ -39,13 +39,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "log.h"
#include "exceptions.h"
#include "settings.h"
+#include "content_sao.h"
+#include "remoteplayer.h"
-Database_PostgreSQL::Database_PostgreSQL(const Settings &conf) :
- m_connect_string(""),
+Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) :
+ m_connect_string(connect_string),
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"
@@ -57,8 +59,6 @@ Database_PostgreSQL::Database_PostgreSQL(const Settings &conf) :
"DELETE rights on the database.\n"
"Don't create mt_user as a SUPERUSER!");
}
-
- connectToDatabase();
}
Database_PostgreSQL::~Database_PostgreSQL()
@@ -118,40 +118,6 @@ bool Database_PostgreSQL::initialized() const
return (PQstatus(m_conn) == CONNECTION_OK);
}
-void Database_PostgreSQL::initStatements()
-{
- 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 "
- "posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4");
-
- prepareStatement("list_all_loadable_blocks",
- "SELECT posX, posY, posZ FROM blocks");
-}
-
PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
{
ExecStatusType statusType = PQresultStatus(result);
@@ -173,30 +139,21 @@ PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
return result;
}
-void Database_PostgreSQL::createDatabase()
+void Database_PostgreSQL::createTableIfNotExists(const std::string &table_name,
+ const std::string &definition)
{
- PGresult *result = checkResults(PQexec(m_conn,
- "SELECT relname FROM pg_class WHERE relname='blocks';"),
- false);
+ std::string sql_check_table = "SELECT relname FROM pg_class WHERE relname='" +
+ table_name + "';";
+ PGresult *result = checkResults(PQexec(m_conn, sql_check_table.c_str()), 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));
+ checkResults(PQexec(m_conn, definition.c_str()));
}
PQclear(result);
-
- infostream << "PostgreSQL: Game Database was inited." << std::endl;
}
-
void Database_PostgreSQL::beginSave()
{
verifyDatabase();
@@ -208,14 +165,70 @@ void Database_PostgreSQL::endSave()
checkResults(PQexec(m_conn, "COMMIT;"));
}
-bool Database_PostgreSQL::saveBlock(const v3s16 &pos,
- const std::string &data)
+MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string):
+ Database_PostgreSQL(connect_string),
+ MapDatabase()
+{
+ connectToDatabase();
+}
+
+
+void MapDatabasePostgreSQL::createDatabase()
+{
+ createTableIfNotExists("blocks",
+ "CREATE TABLE blocks ("
+ "posX INT NOT NULL,"
+ "posY INT NOT NULL,"
+ "posZ INT NOT NULL,"
+ "data BYTEA,"
+ "PRIMARY KEY (posX,posY,posZ)"
+ ");"
+ );
+
+ infostream << "PostgreSQL: Map Database was initialized." << std::endl;
+}
+
+void MapDatabasePostgreSQL::initStatements()
+{
+ prepareStatement("read_block",
+ "SELECT data FROM blocks "
+ "WHERE posX = $1::int4 AND posY = $2::int4 AND "
+ "posZ = $3::int4");
+
+ if (getPGVersion() < 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",
+ "SELECT posX, posY, posZ FROM blocks");
+}
+
+bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data)
{
// Verify if we don't overflow the platform integer with the mapblock size
if (data.size() > INT_MAX) {
errorstream << "Database_PostgreSQL::saveBlock: Data truncation! "
- << "data.size() over 0xFFFF (== " << data.size()
- << ")" << std::endl;
+ << "data.size() over 0xFFFFFFFF (== " << data.size()
+ << ")" << std::endl;
return false;
}
@@ -232,7 +245,7 @@ bool Database_PostgreSQL::saveBlock(const v3s16 &pos,
};
const int argFmt[] = { 1, 1, 1, 1 };
- if (m_pgversion < 90500) {
+ if (getPGVersion() < 90500) {
execPrepared("write_block_update", ARRLEN(args), args, argLen, argFmt);
execPrepared("write_block_insert", ARRLEN(args), args, argLen, argFmt);
} else {
@@ -241,8 +254,7 @@ bool Database_PostgreSQL::saveBlock(const v3s16 &pos,
return true;
}
-void Database_PostgreSQL::loadBlock(const v3s16 &pos,
- std::string *block)
+void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block)
{
verifyDatabase();
@@ -256,19 +268,17 @@ void Database_PostgreSQL::loadBlock(const v3s16 &pos,
const int argFmt[] = { 1, 1, 1 };
PGresult *results = execPrepared("read_block", ARRLEN(args), args,
- argLen, argFmt, false);
+ argLen, argFmt, false);
*block = "";
- if (PQntuples(results)) {
- *block = std::string(PQgetvalue(results, 0, 0),
- PQgetlength(results, 0, 0));
- }
+ if (PQntuples(results))
+ *block = std::string(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0));
PQclear(results);
}
-bool Database_PostgreSQL::deleteBlock(const v3s16 &pos)
+bool MapDatabasePostgreSQL::deleteBlock(const v3s16 &pos)
{
verifyDatabase();
@@ -286,18 +296,338 @@ bool Database_PostgreSQL::deleteBlock(const v3s16 &pos)
return true;
}
-void Database_PostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
+void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
verifyDatabase();
PGresult *results = execPrepared("list_all_loadable_blocks", 0,
- NULL, NULL, NULL, false, false);
+ NULL, NULL, NULL, false, false);
int numrows = PQntuples(results);
- for (int row = 0; row < numrows; ++row) {
+ for (int row = 0; row < numrows; ++row)
dst.push_back(pg_to_v3s16(results, 0, 0));
+
+ PQclear(results);
+}
+
+/*
+ * Player Database
+ */
+PlayerDatabasePostgreSQL::PlayerDatabasePostgreSQL(const std::string &connect_string):
+ Database_PostgreSQL(connect_string),
+ PlayerDatabase()
+{
+ connectToDatabase();
+}
+
+
+void PlayerDatabasePostgreSQL::createDatabase()
+{
+ createTableIfNotExists("player",
+ "CREATE TABLE player ("
+ "name VARCHAR(60) NOT NULL,"
+ "pitch NUMERIC(15, 7) NOT NULL,"
+ "yaw NUMERIC(15, 7) NOT NULL,"
+ "posX NUMERIC(15, 7) NOT NULL,"
+ "posY NUMERIC(15, 7) NOT NULL,"
+ "posZ NUMERIC(15, 7) NOT NULL,"
+ "hp INT NOT NULL,"
+ "breath INT NOT NULL,"
+ "creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
+ "modification_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
+ "PRIMARY KEY (name)"
+ ");"
+ );
+
+ createTableIfNotExists("player_inventories",
+ "CREATE TABLE player_inventories ("
+ "player VARCHAR(60) 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),"
+ "CONSTRAINT player_inventories_fkey FOREIGN KEY (player) REFERENCES "
+ "player (name) ON DELETE CASCADE"
+ ");"
+ );
+
+ createTableIfNotExists("player_inventory_items",
+ "CREATE TABLE player_inventory_items ("
+ "player VARCHAR(60) NOT NULL,"
+ "inv_id INT NOT NULL,"
+ "slot_id INT NOT NULL,"
+ "item TEXT NOT NULL DEFAULT '',"
+ "PRIMARY KEY(player, inv_id, slot_id),"
+ "CONSTRAINT player_inventory_items_fkey FOREIGN KEY (player) REFERENCES "
+ "player (name) ON DELETE CASCADE"
+ ");"
+ );
+
+ createTableIfNotExists("player_metadata",
+ "CREATE TABLE player_metadata ("
+ "player VARCHAR(60) NOT NULL,"
+ "attr VARCHAR(256) NOT NULL,"
+ "value TEXT,"
+ "PRIMARY KEY(player, attr),"
+ "CONSTRAINT player_metadata_fkey FOREIGN KEY (player) REFERENCES "
+ "player (name) ON DELETE CASCADE"
+ ");"
+ );
+
+ infostream << "PostgreSQL: Player Database was inited." << std::endl;
+}
+
+void PlayerDatabasePostgreSQL::initStatements()
+{
+ if (getPGVersion() < 90500) {
+ prepareStatement("create_player",
+ "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
+ "($1, $2, $3, $4, $5, $6, $7::int, $8::int)");
+
+ prepareStatement("update_player",
+ "UPDATE SET pitch = $2, yaw = $3, posX = $4, posY = $5, posZ = $6, hp = $7::int, "
+ "breath = $8::int, modification_date = NOW() WHERE name = $1");
+ } else {
+ prepareStatement("save_player",
+ "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
+ "($1, $2, $3, $4, $5, $6, $7::int, $8::int)"
+ "ON CONFLICT ON CONSTRAINT player_pkey DO UPDATE SET pitch = $2, yaw = $3, "
+ "posX = $4, posY = $5, posZ = $6, hp = $7::int, breath = $8::int, "
+ "modification_date = NOW()");
+ }
+
+ prepareStatement("remove_player", "DELETE FROM player WHERE name = $1");
+
+ prepareStatement("load_player_list", "SELECT name FROM player");
+
+ prepareStatement("remove_player_inventories",
+ "DELETE FROM player_inventories WHERE player = $1");
+
+ prepareStatement("remove_player_inventory_items",
+ "DELETE FROM player_inventory_items WHERE player = $1");
+
+ prepareStatement("add_player_inventory",
+ "INSERT INTO player_inventories (player, inv_id, inv_width, inv_name, inv_size) VALUES "
+ "($1, $2::int, $3::int, $4, $5::int)");
+
+ prepareStatement("add_player_inventory_item",
+ "INSERT INTO player_inventory_items (player, inv_id, slot_id, item) VALUES "
+ "($1, $2::int, $3::int, $4)");
+
+ prepareStatement("load_player_inventories",
+ "SELECT inv_id, inv_width, inv_name, inv_size FROM player_inventories "
+ "WHERE player = $1 ORDER BY inv_id");
+
+ prepareStatement("load_player_inventory_items",
+ "SELECT slot_id, item FROM player_inventory_items WHERE "
+ "player = $1 AND inv_id = $2::int");
+
+ prepareStatement("load_player",
+ "SELECT pitch, yaw, posX, posY, posZ, hp, breath FROM player WHERE name = $1");
+
+ prepareStatement("remove_player_metadata",
+ "DELETE FROM player_metadata WHERE player = $1");
+
+ prepareStatement("save_player_metadata",
+ "INSERT INTO player_metadata (player, attr, value) VALUES ($1, $2, $3)");
+
+ prepareStatement("load_player_metadata",
+ "SELECT attr, value FROM player_metadata WHERE player = $1");
+
+}
+
+bool PlayerDatabasePostgreSQL::playerDataExists(const std::string &playername)
+{
+ verifyDatabase();
+
+ const char *values[] = { playername.c_str() };
+ PGresult *results = execPrepared("load_player", 1, values, false);
+
+ bool res = (PQntuples(results) > 0);
+ PQclear(results);
+ return res;
+}
+
+void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player)
+{
+ PlayerSAO* sao = player->getPlayerSAO();
+ if (!sao)
+ return;
+
+ verifyDatabase();
+
+ v3f pos = sao->getBasePosition();
+ std::string pitch = ftos(sao->getPitch());
+ std::string yaw = ftos(sao->getYaw());
+ std::string posx = ftos(pos.X);
+ std::string posy = ftos(pos.Y);
+ std::string posz = ftos(pos.Z);
+ std::string hp = itos(sao->getHP());
+ std::string breath = itos(sao->getBreath());
+ const char *values[] = {
+ player->getName(),
+ pitch.c_str(),
+ yaw.c_str(),
+ posx.c_str(), posy.c_str(), posz.c_str(),
+ hp.c_str(),
+ breath.c_str()
+ };
+
+ const char* rmvalues[] = { player->getName() };
+ beginSave();
+
+ if (getPGVersion() < 90500) {
+ if (!playerDataExists(player->getName()))
+ execPrepared("create_player", 8, values, true, false);
+ else
+ execPrepared("update_player", 8, values, true, false);
+ }
+ else
+ execPrepared("save_player", 8, values, true, false);
+
+ // Write player inventories
+ execPrepared("remove_player_inventories", 1, rmvalues);
+ execPrepared("remove_player_inventory_items", 1, rmvalues);
+
+ std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists();
+ for (u16 i = 0; i < inventory_lists.size(); i++) {
+ const InventoryList* list = inventory_lists[i];
+ std::string name = list->getName(), width = itos(list->getWidth()),
+ inv_id = itos(i), lsize = itos(list->getSize());
+
+ const char* inv_values[] = {
+ player->getName(),
+ inv_id.c_str(),
+ width.c_str(),
+ name.c_str(),
+ lsize.c_str()
+ };
+ execPrepared("add_player_inventory", 5, inv_values);
+
+ for (u32 j = 0; j < list->getSize(); j++) {
+ std::ostringstream os;
+ list->getItem(j).serialize(os);
+ std::string itemStr = os.str(), slotId = itos(j);
+
+ const char* invitem_values[] = {
+ player->getName(),
+ inv_id.c_str(),
+ slotId.c_str(),
+ itemStr.c_str()
+ };
+ execPrepared("add_player_inventory_item", 4, invitem_values);
+ }
+ }
+
+ execPrepared("remove_player_metadata", 1, rmvalues);
+ const PlayerAttributes &attrs = sao->getExtendedAttributes();
+ for (PlayerAttributes::const_iterator it = attrs.begin(); it != attrs.end(); ++it) {
+ const char *meta_values[] = {
+ player->getName(),
+ it->first.c_str(),
+ it->second.c_str()
+ };
+ execPrepared("save_player_metadata", 3, meta_values);
}
+ endSave();
+}
+
+bool PlayerDatabasePostgreSQL::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
+{
+ sanity_check(sao);
+ verifyDatabase();
+
+ const char *values[] = { player->getName() };
+ PGresult *results = execPrepared("load_player", 1, values, false, false);
+
+ // Player not found, return not found
+ if (!PQntuples(results)) {
+ PQclear(results);
+ return false;
+ }
+
+ sao->setPitch(pg_to_float(results, 0, 0));
+ sao->setYaw(pg_to_float(results, 0, 1));
+ sao->setBasePosition(v3f(
+ pg_to_float(results, 0, 2),
+ pg_to_float(results, 0, 3),
+ pg_to_float(results, 0, 4))
+ );
+ sao->setHPRaw((s16) pg_to_int(results, 0, 5));
+ sao->setBreath((u16) pg_to_int(results, 0, 6), false);
+
+ PQclear(results);
+
+ // Load inventory
+ results = execPrepared("load_player_inventories", 1, values, false, false);
+
+ int resultCount = PQntuples(results);
+
+ for (int row = 0; row < resultCount; ++row) {
+ InventoryList* invList = player->inventory.
+ addList(PQgetvalue(results, row, 2), pg_to_uint(results, row, 3));
+ invList->setWidth(pg_to_uint(results, row, 1));
+
+ u32 invId = pg_to_uint(results, row, 0);
+ std::string invIdStr = itos(invId);
+
+ const char* values2[] = {
+ player->getName(),
+ invIdStr.c_str()
+ };
+ PGresult *results2 = execPrepared("load_player_inventory_items", 2,
+ values2, false, false);
+
+ int resultCount2 = PQntuples(results2);
+ for (int row2 = 0; row2 < resultCount2; row2++) {
+ const std::string itemStr = PQgetvalue(results2, row2, 1);
+ if (itemStr.length() > 0) {
+ ItemStack stack;
+ stack.deSerialize(itemStr);
+ invList->addItem(pg_to_uint(results2, row2, 0), stack);
+ }
+ }
+ PQclear(results2);
+ }
+
+ PQclear(results);
+
+ results = execPrepared("load_player_metadata", 1, values, false);
+
+ int numrows = PQntuples(results);
+ for (int row = 0; row < numrows; row++) {
+ sao->setExtendedAttribute(PQgetvalue(results, row, 0),PQgetvalue(results, row, 1));
+ }
+
+ PQclear(results);
+
+ return true;
+}
+
+bool PlayerDatabasePostgreSQL::removePlayer(const std::string &name)
+{
+ if (!playerDataExists(name))
+ return false;
+
+ verifyDatabase();
+
+ const char *values[] = { name.c_str() };
+ execPrepared("remove_player", 1, values);
+
+ return true;
+}
+
+void PlayerDatabasePostgreSQL::listPlayers(std::vector<std::string> &res)
+{
+ verifyDatabase();
+
+ PGresult *results = execPrepared("load_player_list", 0, NULL, false);
+
+ int numrows = PQntuples(results);
+ for (int row = 0; row < numrows; row++)
+ res.push_back(PQgetvalue(results, row, 0));
PQclear(results);
}
diff --git a/src/database-postgresql.h b/src/database-postgresql.h
index 1cfa544e3..d6f208fd9 100644
--- a/src/database-postgresql.h
+++ b/src/database-postgresql.h
@@ -27,53 +27,33 @@ 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:
- Database_PostgreSQL(const Settings &conf);
+ Database_PostgreSQL(const std::string &connect_string);
~Database_PostgreSQL();
void beginSave();
void endSave();
- bool saveBlock(const v3s16 &pos, const std::string &data);
- void loadBlock(const v3s16 &pos, std::string *block);
- bool deleteBlock(const v3s16 &pos);
- void listAllLoadableBlocks(std::vector<v3s16> &dst);
bool initialized() const;
-private:
- // Database initialization
- void connectToDatabase();
- void initStatements();
- void createDatabase();
- inline void prepareStatement(const std::string &name, const std::string &sql)
+protected:
+ // Conversion helpers
+ inline int pg_to_int(PGresult *res, int row, int col)
{
- checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL));
+ return atoi(PQgetvalue(res, row, col));
}
- // Database connectivity checks
- void ping();
- void verifyDatabase();
-
- // Database usage
- PGresult *checkResults(PGresult *res, bool clear = true);
-
- inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
- const void **params,
- const int *paramsLengths = NULL, const int *paramsFormats = NULL,
- bool clear = true, bool nobinary = true)
+ inline u32 pg_to_uint(PGresult *res, int row, int col)
{
- return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber,
- (const char* const*) params, paramsLengths, paramsFormats,
- nobinary ? 1 : 0), clear);
+ return (u32) atoi(PQgetvalue(res, row, col));
}
- // Conversion helpers
- inline int pg_to_int(PGresult *res, int row, int col)
+ inline float pg_to_float(PGresult *res, int row, int col)
{
- return atoi(PQgetvalue(res, row, col));
+ return (float) atof(PQgetvalue(res, row, col));
}
inline v3s16 pg_to_v3s16(PGresult *res, int row, int col)
@@ -85,11 +65,86 @@ private:
);
}
+ inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
+ const void **params,
+ const int *paramsLengths = NULL, const int *paramsFormats = NULL,
+ bool clear = true, bool nobinary = true)
+ {
+ return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber,
+ (const char* const*) params, paramsLengths, paramsFormats,
+ nobinary ? 1 : 0), clear);
+ }
+
+ inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
+ const char **params, bool clear = true, bool nobinary = true)
+ {
+ return execPrepared(stmtName, paramsNumber,
+ (const void **)params, NULL, NULL, clear, nobinary);
+ }
+
+ void createTableIfNotExists(const std::string &table_name, const std::string &definition);
+ void verifyDatabase();
+
+ // Database initialization
+ void connectToDatabase();
+ virtual void createDatabase() = 0;
+ virtual void initStatements() = 0;
+ inline void prepareStatement(const std::string &name, const std::string &sql)
+ {
+ checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL));
+ }
+
+ const int getPGVersion() const { return m_pgversion; }
+private:
+ // Database connectivity checks
+ void ping();
+
+ // Database usage
+ PGresult *checkResults(PGresult *res, bool clear = true);
+
// Attributes
std::string m_connect_string;
PGconn *m_conn;
int m_pgversion;
};
+class MapDatabasePostgreSQL : private Database_PostgreSQL, public MapDatabase
+{
+public:
+ MapDatabasePostgreSQL(const std::string &connect_string);
+ virtual ~MapDatabasePostgreSQL() {}
+
+ bool saveBlock(const v3s16 &pos, const std::string &data);
+ void loadBlock(const v3s16 &pos, std::string *block);
+ bool deleteBlock(const v3s16 &pos);
+ void listAllLoadableBlocks(std::vector<v3s16> &dst);
+
+ void beginSave() { Database_PostgreSQL::beginSave(); }
+ void endSave() { Database_PostgreSQL::endSave(); }
+
+protected:
+ virtual void createDatabase();
+ virtual void initStatements();
+};
+
+class PlayerDatabasePostgreSQL : private Database_PostgreSQL, public PlayerDatabase
+{
+public:
+ PlayerDatabasePostgreSQL(const std::string &connect_string);
+ virtual ~PlayerDatabasePostgreSQL() {}
+
+ void savePlayer(RemotePlayer *player);
+ bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
+ bool removePlayer(const std::string &name);
+ void listPlayers(std::vector<std::string> &res);
+
+protected:
+ virtual void createDatabase();
+ virtual void initStatements();
+
+private:
+ bool playerDataExists(const std::string &playername);
+};
+
#endif
diff --git a/src/database-redis.h b/src/database-redis.h
index 214bc8dd6..fa15dd8a7 100644
--- a/src/database-redis.h
+++ b/src/database-redis.h
@@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class Settings;
-class Database_Redis : public Database
+class Database_Redis : public MapDatabase
{
public:
Database_Redis(Settings &conf);
diff --git a/src/database-sqlite3.cpp b/src/database-sqlite3.cpp
index 095d485c0..714f56c39 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 <cassert>
@@ -111,27 +113,26 @@ int Database_SQLite3::busyHandler(void *data, int count)
}
-Database_SQLite3::Database_SQLite3(const std::string &savedir) :
+Database_SQLite3::Database_SQLite3(const std::string &savedir, const std::string &dbname) :
+ m_database(NULL),
m_initialized(false),
m_savedir(savedir),
- m_database(NULL),
- m_stmt_read(NULL),
- m_stmt_write(NULL),
- m_stmt_list(NULL),
- m_stmt_delete(NULL),
+ m_dbname(dbname),
m_stmt_begin(NULL),
m_stmt_end(NULL)
{
}
-void Database_SQLite3::beginSave() {
+void Database_SQLite3::beginSave()
+{
verifyDatabase();
SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE,
"Failed to start SQLite3 transaction");
sqlite3_reset(m_stmt_begin);
}
-void Database_SQLite3::endSave() {
+void Database_SQLite3::endSave()
+{
verifyDatabase();
SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE,
"Failed to commit SQLite3 transaction");
@@ -142,7 +143,7 @@ void Database_SQLite3::openDatabase()
{
if (m_database) return;
- std::string dbp = m_savedir + DIR_DELIM + "map.sqlite";
+ std::string dbp = m_savedir + DIR_DELIM + m_dbname + ".sqlite";
// Open the database connection
@@ -170,6 +171,8 @@ void Database_SQLite3::openDatabase()
+ itos(g_settings->getU16("sqlite_synchronous"));
SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL),
"Failed to modify sqlite3 synchronous mode");
+ SQLOK(sqlite3_exec(m_database, "PRAGMA foreign_keys = ON", NULL, NULL, NULL),
+ "Failed to enable sqlite3 foreign key support");
}
void Database_SQLite3::verifyDatabase()
@@ -178,8 +181,61 @@ void Database_SQLite3::verifyDatabase()
openDatabase();
- PREPARE_STATEMENT(begin, "BEGIN");
- PREPARE_STATEMENT(end, "COMMIT");
+ PREPARE_STATEMENT(begin, "BEGIN;");
+ PREPARE_STATEMENT(end, "COMMIT;");
+
+ initStatements();
+
+ m_initialized = true;
+}
+
+Database_SQLite3::~Database_SQLite3()
+{
+ FINALIZE_STATEMENT(m_stmt_begin)
+ FINALIZE_STATEMENT(m_stmt_end)
+
+ SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database");
+}
+
+/*
+ * Map database
+ */
+
+MapDatabaseSQLite3::MapDatabaseSQLite3(const std::string &savedir):
+ Database_SQLite3(savedir, "map"),
+ MapDatabase(),
+ m_stmt_read(NULL),
+ m_stmt_write(NULL),
+ m_stmt_list(NULL),
+ m_stmt_delete(NULL)
+{
+
+}
+
+MapDatabaseSQLite3::~MapDatabaseSQLite3()
+{
+ FINALIZE_STATEMENT(m_stmt_read)
+ FINALIZE_STATEMENT(m_stmt_write)
+ FINALIZE_STATEMENT(m_stmt_list)
+ FINALIZE_STATEMENT(m_stmt_delete)
+}
+
+
+void MapDatabaseSQLite3::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",
+ NULL, NULL, NULL),
+ "Failed to create database table");
+}
+
+void MapDatabaseSQLite3::initStatements()
+{
PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1");
#ifdef __ANDROID__
PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
@@ -189,18 +245,16 @@ void Database_SQLite3::verifyDatabase()
PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?");
PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`");
- m_initialized = true;
-
verbosestream << "ServerMap: SQLite3 database opened." << std::endl;
}
-inline void Database_SQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index)
+inline void MapDatabaseSQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index)
{
SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
}
-bool Database_SQLite3::deleteBlock(const v3s16 &pos)
+bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos)
{
verifyDatabase();
@@ -216,7 +270,7 @@ bool Database_SQLite3::deleteBlock(const v3s16 &pos)
return good;
}
-bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data)
+bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data)
{
verifyDatabase();
@@ -243,7 +297,7 @@ bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data)
return true;
}
-void Database_SQLite3::loadBlock(const v3s16 &pos, std::string *block)
+void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block)
{
verifyDatabase();
@@ -264,37 +318,312 @@ void Database_SQLite3::loadBlock(const v3s16 &pos, std::string *block)
sqlite3_reset(m_stmt_read);
}
-void Database_SQLite3::createDatabase()
+void MapDatabaseSQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst)
+{
+ verifyDatabase();
+
+ while (sqlite3_step(m_stmt_list) == SQLITE_ROW)
+ dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0)));
+
+ sqlite3_reset(m_stmt_list);
+}
+
+/*
+ * Player Database
+ */
+
+PlayerDatabaseSQLite3::PlayerDatabaseSQLite3(const std::string &savedir):
+ Database_SQLite3(savedir, "players"),
+ PlayerDatabase(),
+ m_stmt_player_load(NULL),
+ m_stmt_player_add(NULL),
+ m_stmt_player_update(NULL),
+ m_stmt_player_remove(NULL),
+ m_stmt_player_list(NULL),
+ m_stmt_player_load_inventory(NULL),
+ m_stmt_player_load_inventory_items(NULL),
+ m_stmt_player_add_inventory(NULL),
+ m_stmt_player_add_inventory_items(NULL),
+ m_stmt_player_remove_inventory(NULL),
+ m_stmt_player_remove_inventory_items(NULL),
+ m_stmt_player_metadata_load(NULL),
+ m_stmt_player_metadata_remove(NULL),
+ m_stmt_player_metadata_add(NULL)
+{
+
+}
+PlayerDatabaseSQLite3::~PlayerDatabaseSQLite3()
+{
+ FINALIZE_STATEMENT(m_stmt_player_load)
+ FINALIZE_STATEMENT(m_stmt_player_add)
+ FINALIZE_STATEMENT(m_stmt_player_update)
+ FINALIZE_STATEMENT(m_stmt_player_remove)
+ FINALIZE_STATEMENT(m_stmt_player_list)
+ 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)
+};
+
+
+void PlayerDatabaseSQLite3::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",
+ "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,"
+ "`creation_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
+ "`modification_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
+ "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_metadata` ("
+ " `player` VARCHAR(50) NOT NULL,"
+ " `metadata` VARCHAR(256) NOT NULL,"
+ " `value` TEXT,"
+ " PRIMARY KEY(`player`, `metadata`),"
+ " FOREIGN KEY (`player`) REFERENCES player (`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 player (`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 player (`name`) ON DELETE CASCADE );",
+ NULL, NULL, NULL),
+ "Failed to create player inventory items table");
}
-void Database_SQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst)
+void PlayerDatabaseSQLite3::initStatements()
+{
+ 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` = ?, "
+ "`modification_date` = CURRENT_TIMESTAMP WHERE `name` = ?")
+ PREPARE_STATEMENT(player_remove, "DELETE FROM `player` WHERE `name` = ?")
+ PREPARE_STATEMENT(player_list, "SELECT `name` FROM `player`")
+
+ 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_metadata` WHERE `player` = ?")
+ PREPARE_STATEMENT(player_metadata_add, "INSERT INTO `player_metadata` "
+ "(`player`, `metadata`, `value`) VALUES (?, ?, ?)")
+ PREPARE_STATEMENT(player_metadata_remove, "DELETE FROM `player_metadata` "
+ "WHERE `player` = ?")
+ verbosestream << "ServerEnvironment: SQLite3 database opened (players)." << std::endl;
+}
+
+bool PlayerDatabaseSQLite3::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;
+}
- while (sqlite3_step(m_stmt_list) == SQLITE_ROW) {
- dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0)));
+void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player)
+{
+ PlayerSAO* sao = player->getPlayerSAO();
+ sanity_check(sao);
+
+ 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);
+ int64_to_sqlite(m_stmt_player_update, 6, sao->getHP());
+ int64_to_sqlite(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);
}
- sqlite3_reset(m_stmt_list);
+
+ // 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<const InventoryList*> 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);
+ }
+ }
+
+ 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();
}
-Database_SQLite3::~Database_SQLite3()
+bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
{
- FINALIZE_STATEMENT(m_stmt_read)
- FINALIZE_STATEMENT(m_stmt_write)
- FINALIZE_STATEMENT(m_stmt_list)
- FINALIZE_STATEMENT(m_stmt_begin)
- FINALIZE_STATEMENT(m_stmt_end)
- FINALIZE_STATEMENT(m_stmt_delete)
+ verifyDatabase();
- SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database");
+ str_to_sqlite(m_stmt_player_load, 1, player->getName());
+ if (sqlite3_step(m_stmt_player_load) != SQLITE_ROW) {
+ sqlite3_reset(m_stmt_player_load);
+ return false;
+ }
+ sao->setPitch(sqlite_to_float(m_stmt_player_load, 0));
+ sao->setYaw(sqlite_to_float(m_stmt_player_load, 1));
+ sao->setBasePosition(sqlite_to_v3f(m_stmt_player_load, 2));
+ sao->setHPRaw((s16) MYMIN(sqlite_to_int(m_stmt_player_load, 5), S16_MAX));
+ sao->setBreath((u16) MYMIN(sqlite_to_int(m_stmt_player_load, 6), U16_MAX), false);
+ sqlite3_reset(m_stmt_player_load);
+
+ // Load inventory
+ str_to_sqlite(m_stmt_player_load_inventory, 1, player->getName());
+ while (sqlite3_step(m_stmt_player_load_inventory) == SQLITE_ROW) {
+ InventoryList *invList = player->inventory.addList(
+ sqlite_to_string(m_stmt_player_load_inventory, 2),
+ sqlite_to_uint(m_stmt_player_load_inventory, 3));
+ invList->setWidth(sqlite_to_uint(m_stmt_player_load_inventory, 1));
+
+ u32 invId = sqlite_to_uint(m_stmt_player_load_inventory, 0);
+
+ str_to_sqlite(m_stmt_player_load_inventory_items, 1, player->getName());
+ int_to_sqlite(m_stmt_player_load_inventory_items, 2, invId);
+ while (sqlite3_step(m_stmt_player_load_inventory_items) == SQLITE_ROW) {
+ const std::string itemStr = sqlite_to_string(m_stmt_player_load_inventory_items, 1);
+ if (itemStr.length() > 0) {
+ ItemStack stack;
+ stack.deSerialize(itemStr);
+ invList->addItem(sqlite_to_uint(m_stmt_player_load_inventory_items, 0), stack);
+ }
+ }
+ sqlite3_reset(m_stmt_player_load_inventory_items);
+ }
+
+ sqlite3_reset(m_stmt_player_load_inventory);
+
+ str_to_sqlite(m_stmt_player_metadata_load, 1, sao->getPlayer()->getName());
+ while (sqlite3_step(m_stmt_player_metadata_load) == SQLITE_ROW) {
+ std::string attr = sqlite_to_string(m_stmt_player_metadata_load, 0);
+ std::string value = sqlite_to_string(m_stmt_player_metadata_load, 1);
+
+ sao->setExtendedAttribute(attr, value);
+ }
+ sqlite3_reset(m_stmt_player_metadata_load);
+ return true;
+}
+
+bool PlayerDatabaseSQLite3::removePlayer(const std::string &name)
+{
+ if (!playerDataExists(name))
+ return false;
+
+ str_to_sqlite(m_stmt_player_remove, 1, name);
+ sqlite3_vrfy(sqlite3_step(m_stmt_player_remove), SQLITE_DONE);
+ sqlite3_reset(m_stmt_player_remove);
+ return true;
}
+void PlayerDatabaseSQLite3::listPlayers(std::vector<std::string> &res)
+{
+ verifyDatabase();
+
+ while (sqlite3_step(m_stmt_player_list) == SQLITE_ROW)
+ res.push_back(sqlite_to_string(m_stmt_player_list, 0));
+
+ sqlite3_reset(m_stmt_player_list);
+}
diff --git a/src/database-sqlite3.h b/src/database-sqlite3.h
index 2ab4c8ee9..3244facc9 100644
--- a/src/database-sqlite3.h
+++ b/src/database-sqlite3.h
@@ -20,8 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#ifndef DATABASE_SQLITE3_HEADER
#define DATABASE_SQLITE3_HEADER
+#include <cstring>
#include <string>
#include "database.h"
+#include "exceptions.h"
extern "C" {
#include "sqlite3.h"
@@ -30,37 +32,97 @@ extern "C" {
class Database_SQLite3 : public Database
{
public:
- Database_SQLite3(const std::string &savedir);
- ~Database_SQLite3();
+ virtual ~Database_SQLite3();
void beginSave();
void endSave();
- bool saveBlock(const v3s16 &pos, const std::string &data);
- void loadBlock(const v3s16 &pos, std::string *block);
- bool deleteBlock(const v3s16 &pos);
- void listAllLoadableBlocks(std::vector<v3s16> &dst);
bool initialized() const { return m_initialized; }
+protected:
+ Database_SQLite3(const std::string &savedir, const std::string &dbname);
-private:
- // Open the database
- void openDatabase();
- // Create the database structure
- void createDatabase();
// Open and initialize the database if needed
void verifyDatabase();
- void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index = 1);
+ // Convertors
+ inline void str_to_sqlite(sqlite3_stmt *s, 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, 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, int iCol, int val) const
+ {
+ sqlite3_vrfy(sqlite3_bind_int(s, iCol, val));
+ }
+
+ inline void int64_to_sqlite(sqlite3_stmt *s, int iCol, s64 val) const
+ {
+ sqlite3_vrfy(sqlite3_bind_int64(s, iCol, (sqlite3_int64) val));
+ }
+
+ inline void double_to_sqlite(sqlite3_stmt *s, int iCol, double val) const
+ {
+ sqlite3_vrfy(sqlite3_bind_double(s, iCol, val));
+ }
+
+ inline std::string sqlite_to_string(sqlite3_stmt *s, int iCol)
+ {
+ const char* text = reinterpret_cast<const char*>(sqlite3_column_text(s, iCol));
+ return std::string(text ? text : "");
+ }
+
+ inline s32 sqlite_to_int(sqlite3_stmt *s, int iCol)
+ {
+ return sqlite3_column_int(s, iCol);
+ }
+
+ inline u32 sqlite_to_uint(sqlite3_stmt *s, int iCol)
+ {
+ return (u32) sqlite3_column_int(s, iCol);
+ }
+
+ inline float sqlite_to_float(sqlite3_stmt *s, int iCol)
+ {
+ return (float) sqlite3_column_double(s, iCol);
+ }
+
+ inline const v3f sqlite_to_v3f(sqlite3_stmt *s, int iCol)
+ {
+ return v3f(sqlite_to_float(s, iCol), sqlite_to_float(s, iCol + 1),
+ sqlite_to_float(s, iCol + 2));
+ }
+
+ // Query verifiers helpers
+ inline void sqlite3_vrfy(int s, const std::string &m = "", int r = SQLITE_OK) const
+ {
+ if (s != r)
+ throw DatabaseException(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);
+ }
+
+ // Create the database structure
+ virtual void createDatabase() = 0;
+ virtual void initStatements() = 0;
+
+ sqlite3 *m_database;
+private:
+ // Open the database
+ void openDatabase();
bool m_initialized;
std::string m_savedir;
+ std::string m_dbname;
- sqlite3 *m_database;
- sqlite3_stmt *m_stmt_read;
- sqlite3_stmt *m_stmt_write;
- sqlite3_stmt *m_stmt_list;
- sqlite3_stmt *m_stmt_delete;
sqlite3_stmt *m_stmt_begin;
sqlite3_stmt *m_stmt_end;
@@ -69,4 +131,66 @@ private:
static int busyHandler(void *data, int count);
};
+class MapDatabaseSQLite3 : private Database_SQLite3, public MapDatabase
+{
+public:
+ MapDatabaseSQLite3(const std::string &savedir);
+ virtual ~MapDatabaseSQLite3();
+
+ bool saveBlock(const v3s16 &pos, const std::string &data);
+ void loadBlock(const v3s16 &pos, std::string *block);
+ bool deleteBlock(const v3s16 &pos);
+ void listAllLoadableBlocks(std::vector<v3s16> &dst);
+
+ void beginSave() { Database_SQLite3::beginSave(); }
+ void endSave() { Database_SQLite3::endSave(); }
+protected:
+ virtual void createDatabase();
+ virtual void initStatements();
+
+private:
+ void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index = 1);
+
+ // Map
+ sqlite3_stmt *m_stmt_read;
+ sqlite3_stmt *m_stmt_write;
+ sqlite3_stmt *m_stmt_list;
+ sqlite3_stmt *m_stmt_delete;
+};
+
+class PlayerDatabaseSQLite3 : private Database_SQLite3, public PlayerDatabase
+{
+public:
+ PlayerDatabaseSQLite3(const std::string &savedir);
+ virtual ~PlayerDatabaseSQLite3();
+
+ void savePlayer(RemotePlayer *player);
+ bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
+ bool removePlayer(const std::string &name);
+ void listPlayers(std::vector<std::string> &res);
+
+protected:
+ virtual void createDatabase();
+ virtual void initStatements();
+
+private:
+ bool playerDataExists(const std::string &name);
+
+ // Players
+ sqlite3_stmt *m_stmt_player_load;
+ sqlite3_stmt *m_stmt_player_add;
+ sqlite3_stmt *m_stmt_player_update;
+ sqlite3_stmt *m_stmt_player_remove;
+ sqlite3_stmt *m_stmt_player_list;
+ 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;
+};
+
#endif
diff --git a/src/database.cpp b/src/database.cpp
index 262d475ec..8e1483893 100644
--- a/src/database.cpp
+++ b/src/database.cpp
@@ -48,7 +48,7 @@ static inline s64 pythonmodulo(s64 i, s16 mod)
}
-s64 Database::getBlockAsInteger(const v3s16 &pos)
+s64 MapDatabase::getBlockAsInteger(const v3s16 &pos)
{
return (u64) pos.Z * 0x1000000 +
(u64) pos.Y * 0x1000 +
@@ -56,7 +56,7 @@ s64 Database::getBlockAsInteger(const v3s16 &pos)
}
-v3s16 Database::getIntegerAsBlock(s64 i)
+v3s16 MapDatabase::getIntegerAsBlock(s64 i)
{
v3s16 pos;
pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
diff --git a/src/database.h b/src/database.h
index 7213f088a..5a2b844fd 100644
--- a/src/database.h
+++ b/src/database.h
@@ -29,10 +29,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class Database
{
public:
- virtual ~Database() {}
+ virtual void beginSave() = 0;
+ virtual void endSave() = 0;
+ virtual bool initialized() const { return true; }
+};
- virtual void beginSave() {}
- virtual void endSave() {}
+class MapDatabase : public Database
+{
+public:
+ virtual ~MapDatabase() {}
virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0;
virtual void loadBlock(const v3s16 &pos, std::string *block) = 0;
@@ -42,8 +47,19 @@ public:
static v3s16 getIntegerAsBlock(s64 i);
virtual void listAllLoadableBlocks(std::vector<v3s16> &dst) = 0;
+};
- virtual bool initialized() const { return true; }
+class PlayerSAO;
+class RemotePlayer;
+
+class PlayerDatabase
+{
+public:
+ virtual ~PlayerDatabase() {}
+ virtual void savePlayer(RemotePlayer *player) = 0;
+ virtual bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) = 0;
+ virtual bool removePlayer(const std::string &name) = 0;
+ virtual void listPlayers(std::vector<std::string> &res) = 0;
};
#endif
diff --git a/src/main.cpp b/src/main.cpp
index 1ec278981..2ad4e2780 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#endif
#ifndef SERVER
#include "client/clientlauncher.h"
+
#endif
#ifdef HAVE_TOUCHSCREENGUI
@@ -102,7 +103,7 @@ static bool get_game_from_cmdline(GameParams *game_params, const Settings &cmd_a
static bool determine_subgame(GameParams *game_params);
static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args);
-static bool migrate_database(const GameParams &game_params, const Settings &cmd_args);
+static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args);
/**********************************************************************/
@@ -292,6 +293,8 @@ static void set_allowed_options(OptionList *allowed_options)
_("Set gameid (\"--gameid list\" prints available ones)"))));
allowed_options->insert(std::make_pair("migrate", ValueSpec(VALUETYPE_STRING,
_("Migrate from current map backend to another (Only works when using minetestserver or with --server)"))));
+ allowed_options->insert(std::make_pair("migrate-players", ValueSpec(VALUETYPE_STRING,
+ _("Migrate from current players backend to another (Only works when using minetestserver or with --server)"))));
allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG,
_("Feature an interactive terminal (Only works when using minetestserver or with --server)"))));
#ifndef SERVER
@@ -332,7 +335,7 @@ static void print_allowed_options(const OptionList &allowed_options)
if (i->second.type != VALUETYPE_FLAG)
os1 << _(" <value>");
- std::cout << padStringRight(os1.str(), 24);
+ std::cout << padStringRight(os1.str(), 30);
if (i->second.help != NULL)
std::cout << i->second.help;
@@ -828,7 +831,9 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
// Database migration
if (cmd_args.exists("migrate"))
- return migrate_database(game_params, cmd_args);
+ return migrate_map_database(game_params, cmd_args);
+ else if (cmd_args.exists("migrate-players"))
+ return ServerEnvironment::migratePlayersDatabase(game_params, cmd_args);
if (cmd_args.exists("terminal")) {
#if USE_CURSES
@@ -912,7 +917,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
return true;
}
-static bool migrate_database(const GameParams &game_params, const Settings &cmd_args)
+static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args)
{
std::string migrate_to = cmd_args.get("migrate");
Settings world_mt;
@@ -921,20 +926,23 @@ static bool migrate_database(const GameParams &game_params, const Settings &cmd_
errorstream << "Cannot read world.mt!" << std::endl;
return false;
}
+
if (!world_mt.exists("backend")) {
errorstream << "Please specify your current backend in world.mt:"
<< std::endl
- << " backend = {sqlite3|leveldb|redis|dummy}"
+ << " backend = {sqlite3|leveldb|redis|dummy|postgresql}"
<< std::endl;
return false;
}
+
std::string backend = world_mt.get("backend");
if (backend == migrate_to) {
errorstream << "Cannot migrate: new backend is same"
<< " as the old one" << std::endl;
return false;
}
- Database *old_db = ServerMap::createDatabase(backend, game_params.world_path, world_mt),
+
+ MapDatabase *old_db = ServerMap::createDatabase(backend, game_params.world_path, world_mt),
*new_db = ServerMap::createDatabase(migrate_to, game_params.world_path, world_mt);
u32 count = 0;
@@ -976,4 +984,3 @@ static bool migrate_database(const GameParams &game_params, const Settings &cmd_
return true;
}
-
diff --git a/src/map.cpp b/src/map.cpp
index 75dcee350..c148c51f1 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -2286,13 +2286,13 @@ bool ServerMap::loadSectorFull(v2s16 p2d)
}
#endif
-Database *ServerMap::createDatabase(
+MapDatabase *ServerMap::createDatabase(
const std::string &name,
const std::string &savedir,
Settings &conf)
{
if (name == "sqlite3")
- return new Database_SQLite3(savedir);
+ return new MapDatabaseSQLite3(savedir);
if (name == "dummy")
return new Database_Dummy();
#if USE_LEVELDB
@@ -2304,8 +2304,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 MapDatabasePostgreSQL(connect_string);
+ }
#endif
else
throw BaseException(std::string("Database backend ") + name + " not supported.");
@@ -2326,7 +2329,7 @@ bool ServerMap::saveBlock(MapBlock *block)
return saveBlock(block, dbase);
}
-bool ServerMap::saveBlock(MapBlock *block, Database *db)
+bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db)
{
v3s16 p3d = block->getPos();
diff --git a/src/map.h b/src/map.h
index 4d7079823..7e597bef6 100644
--- a/src/map.h
+++ b/src/map.h
@@ -37,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "map_settings_manager.h"
class Settings;
-class Database;
+class MapDatabase;
class ClientMap;
class MapSector;
class ServerMapSector;
@@ -430,7 +430,7 @@ public:
/*
Database functions
*/
- static Database *createDatabase(const std::string &name, const std::string &savedir, Settings &conf);
+ static MapDatabase *createDatabase(const std::string &name, const std::string &savedir, Settings &conf);
// Returns true if the database file does not exist
bool loadFromFolders();
@@ -458,7 +458,7 @@ public:
bool loadSectorMeta(v2s16 p2d);
bool saveBlock(MapBlock *block);
- static bool saveBlock(MapBlock *block, Database *db);
+ static bool saveBlock(MapBlock *block, MapDatabase *db);
// This will generate a sector with getSector if not found.
void loadBlock(const std::string &sectordir, const std::string &blockfile,
MapSector *sector, bool save_after_load=false);
@@ -510,7 +510,7 @@ private:
This is reset to false when written on disk.
*/
bool m_map_metadata_changed;
- Database *dbase;
+ MapDatabase *dbase;
};
diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp
index c8e5b9132..2dbfe9d9d 100644
--- a/src/remoteplayer.cpp
+++ b/src/remoteplayer.cpp
@@ -67,54 +67,6 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef):
movement_gravity = g_settings->getFloat("movement_gravity") * BS;
}
-void RemotePlayer::save(std::string savedir, IGameDef *gamedef)
-{
- /*
- * We have to open all possible player files in the players directory
- * and check their player names because some file systems are not
- * case-sensitive and player names are case-sensitive.
- */
-
- // A player to deserialize files into to check their names
- RemotePlayer testplayer("", gamedef->idef());
-
- savedir += DIR_DELIM;
- std::string path = savedir + m_name;
- for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
- if (!fs::PathExists(path)) {
- // Open file and serialize
- std::ostringstream ss(std::ios_base::binary);
- serialize(ss);
- if (!fs::safeWriteToFile(path, ss.str())) {
- infostream << "Failed to write " << path << std::endl;
- }
- setModified(false);
- return;
- }
- // Open file and deserialize
- std::ifstream is(path.c_str(), std::ios_base::binary);
- if (!is.good()) {
- infostream << "Failed to open " << path << std::endl;
- return;
- }
- testplayer.deSerialize(is, path, NULL);
- is.close();
- if (strcmp(testplayer.getName(), m_name) == 0) {
- // Open file and serialize
- std::ostringstream ss(std::ios_base::binary);
- serialize(ss);
- if (!fs::safeWriteToFile(path, ss.str())) {
- infostream << "Failed to write " << path << std::endl;
- }
- setModified(false);
- return;
- }
- path = savedir + m_name + itos(i);
- }
-
- infostream << "Didn't find free file for player " << m_name << std::endl;
-}
-
void RemotePlayer::serializeExtraAttributes(std::string &output)
{
assert(m_sao);
diff --git a/src/remoteplayer.h b/src/remoteplayer.h
index 9d123393f..ce7db5608 100644
--- a/src/remoteplayer.h
+++ b/src/remoteplayer.h
@@ -37,11 +37,11 @@ enum RemotePlayerChatResult
*/
class RemotePlayer : public Player
{
+ friend class PlayerDatabaseFiles;
public:
RemotePlayer(const char *name, IItemDefManager *idef);
virtual ~RemotePlayer() {}
- void save(std::string savedir, IGameDef *gamedef);
void deSerialize(std::istream &is, const std::string &playername, PlayerSAO *sao);
PlayerSAO *getPlayerSAO() { return m_sao; }
diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp
index 6ac4bb653..d94f3e31d 100644
--- a/src/script/lua_api/l_server.cpp
+++ b/src/script/lua_api/l_server.cpp
@@ -334,6 +334,22 @@ int ModApiServer::l_kick_player(lua_State *L)
return 1;
}
+int ModApiServer::l_remove_player(lua_State *L)
+{
+ NO_MAP_LOCK_REQUIRED;
+ std::string name = luaL_checkstring(L, 1);
+ ServerEnvironment *s_env = dynamic_cast<ServerEnvironment *>(getEnv(L));
+ assert(s_env);
+
+ RemotePlayer *player = s_env->getPlayer(name.c_str());
+ if (!player)
+ lua_pushinteger(L, s_env->removePlayerFromDatabase(name) ? 0 : 1);
+ else
+ lua_pushinteger(L, 2);
+
+ return 1;
+}
+
// unban_player_or_ip()
int ModApiServer::l_unban_player_or_ip(lua_State *L)
{
@@ -510,6 +526,7 @@ void ModApiServer::Initialize(lua_State *L, int top)
API_FCT(get_ban_description);
API_FCT(ban_player);
API_FCT(kick_player);
+ API_FCT(remove_player);
API_FCT(unban_player_or_ip);
API_FCT(notify_authentication_modified);
diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h
index 008810784..e6c0df978 100644
--- a/src/script/lua_api/l_server.h
+++ b/src/script/lua_api/l_server.h
@@ -92,6 +92,9 @@ private:
// kick_player(name, [message]) -> success
static int l_kick_player(lua_State *L);
+ // remove_player(name)
+ static int l_remove_player(lua_State *L);
+
// notify_authentication_modified(name)
static int l_notify_authentication_modified(lua_State *L);
diff --git a/src/server.cpp b/src/server.cpp
index ac6265d09..4c7e60286 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -60,6 +60,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/base64.h"
#include "util/sha1.h"
#include "util/hex.h"
+#include "database.h"
class ClientNotFoundException : public BaseException
{
@@ -2618,9 +2619,8 @@ void Server::RespawnPlayer(u16 peer_id)
bool repositioned = m_script->on_respawnplayer(playersao);
if (!repositioned) {
- v3f pos = findSpawnPos();
// setPos will send the new position to client
- playersao->setPos(pos);
+ playersao->setPos(findSpawnPos());
}
SendPlayerHP(peer_id);
@@ -3442,8 +3442,8 @@ v3f Server::findSpawnPos()
s32 range = 1 + i;
// We're going to try to throw the player to this position
v2s16 nodepos2d = v2s16(
- -range + (myrand() % (range * 2)),
- -range + (myrand() % (range * 2)));
+ -range + (myrand() % (range * 2)),
+ -range + (myrand() % (range * 2)));
// Get spawn level at point
s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d);
@@ -3516,8 +3516,6 @@ void Server::requestShutdown(const std::string &msg, bool reconnect, float delay
PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version)
{
- bool newplayer = false;
-
/*
Try to get an existing player
*/
@@ -3538,44 +3536,18 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version
return NULL;
}
- // Create a new player active object
- PlayerSAO *playersao = new PlayerSAO(m_env, peer_id, isSingleplayer());
- player = m_env->loadPlayer(name, playersao);
-
- // Create player if it doesn't exist
if (!player) {
- newplayer = true;
- player = new RemotePlayer(name, this->idef());
- // Set player position
- infostream<<"Server: Finding spawn place for player \""
- <<name<<"\""<<std::endl;
- playersao->setBasePosition(findSpawnPos());
-
- // Make sure the player is saved
- player->setModified(true);
-
- // Add player to environment
- m_env->addPlayer(player);
- } else {
- // If the player exists, ensure that they respawn inside legal bounds
- // This fixes an assert crash when the player can't be added
- // to the environment
- if (objectpos_over_limit(playersao->getBasePosition())) {
- actionstream << "Respawn position for player \""
- << name << "\" outside limits, resetting" << std::endl;
- playersao->setBasePosition(findSpawnPos());
- }
+ player = new RemotePlayer(name, idef());
}
- playersao->initialize(player, getPlayerEffectivePrivs(player->getName()));
-
- player->protocol_version = proto_version;
+ bool newplayer = false;
- /* Clean up old HUD elements from previous sessions */
- player->clearHud();
+ // Load player
+ PlayerSAO *playersao = m_env->loadPlayer(player, &newplayer, peer_id, isSingleplayer());
- /* Add object to environment */
- m_env->addActiveObject(playersao);
+ // Complete init with server parts
+ playersao->finalize(player, getPlayerEffectivePrivs(player->getName()));
+ player->protocol_version = proto_version;
/* Run scripts */
if (newplayer) {
diff --git a/src/server.h b/src/server.h
index 4183bcda1..948fb8fc2 100644
--- a/src/server.h
+++ b/src/server.h
@@ -306,6 +306,7 @@ public:
bool showFormspec(const char *name, const std::string &formspec, const std::string &formname);
Map & getMap() { return m_env->getMap(); }
ServerEnvironment & getEnv() { return *m_env; }
+ v3f findSpawnPos();
u32 hudAdd(RemotePlayer *player, HudElement *element);
bool hudRemove(RemotePlayer *player, u32 id);
@@ -472,8 +473,6 @@ private:
RemotePlayer *player = NULL);
void handleAdminChat(const ChatEventChat *evt);
- v3f findSpawnPos();
-
// When called, connection mutex should be locked
RemoteClient* getClient(u16 peer_id,ClientState state_min=CS_Active);
RemoteClient* getClientNoEx(u16 peer_id,ClientState state_min=CS_Active);
diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp
index e09c7da16..c0dc0e0ea 100644
--- a/src/serverenvironment.cpp
+++ b/src/serverenvironment.cpp
@@ -36,6 +36,13 @@ 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 "gameparams.h"
+#include "database-dummy.h"
+#include "database-files.h"
+#include "database-sqlite3.h"
+#if USE_POSTGRESQL
+#include "database-postgresql.h"
+#endif
#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
@@ -365,8 +372,30 @@ 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)
{
+ // Determine which database backend to use
+ std::string conf_path = path_world + DIR_DELIM + "world.mt";
+ Settings conf;
+ bool succeeded = conf.readConfigFile(conf_path.c_str());
+ if (!succeeded || !conf.exists("player_backend")) {
+ // fall back to files
+ conf.set("player_backend", "files");
+ warningstream << "/!\\ You are using old player file backend. "
+ << "This backend is deprecated and will be removed in next release /!\\"
+ << std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
+ << "please read http://wiki.minetest.net/Database_backends." << std::endl;
+
+ if (!conf.updateConfigFile(conf_path.c_str())) {
+ errorstream << "ServerEnvironment::ServerEnvironment(): "
+ << "Failed to update world.mt!" << std::endl;
+ }
+ }
+
+ std::string name = "";
+ conf.getNoEx("player_backend", name);
+ m_player_database = openPlayerDatabase(name, path_world, conf);
}
ServerEnvironment::~ServerEnvironment()
@@ -392,6 +421,8 @@ ServerEnvironment::~ServerEnvironment()
i != m_players.end(); ++i) {
delete (*i);
}
+
+ delete m_player_database;
}
Map & ServerEnvironment::getMap()
@@ -455,6 +486,11 @@ void ServerEnvironment::removePlayer(RemotePlayer *player)
}
}
+bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
+{
+ return m_player_database->removePlayer(name);
+}
+
bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 *p)
{
float distance = pos1.getDistanceFrom(pos2);
@@ -495,7 +531,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<RemotePlayer *>::iterator it = m_players.begin();
@@ -503,63 +539,63 @@ void ServerEnvironment::saveLoadedPlayers()
++it) {
if ((*it)->checkModified() ||
((*it)->getPlayerSAO() && (*it)->getPlayerSAO()->extendedAttributesModified())) {
- (*it)->save(players_path, m_server);
+ try {
+ m_player_database->savePlayer(*it);
+ } catch (DatabaseException &e) {
+ errorstream << "Failed to save player " << (*it)->getName() << " exception: "
+ << e.what() << std::endl;
+ throw;
+ }
}
}
}
void ServerEnvironment::savePlayer(RemotePlayer *player)
{
- std::string players_path = m_path_world + DIR_DELIM "players";
- fs::CreateDir(players_path);
-
- player->save(players_path, m_server);
+ try {
+ m_player_database->savePlayer(player);
+ } catch (DatabaseException &e) {
+ errorstream << "Failed to save player " << player->getName() << " exception: "
+ << e.what() << std::endl;
+ throw;
+ }
}
-RemotePlayer *ServerEnvironment::loadPlayer(const std::string &playername, PlayerSAO *sao)
+PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
+ u16 peer_id, bool is_singleplayer)
{
- bool newplayer = false;
- bool found = false;
- std::string players_path = m_path_world + DIR_DELIM "players" DIR_DELIM;
- std::string path = players_path + playername;
-
- RemotePlayer *player = getPlayer(playername.c_str());
- if (!player) {
- player = new RemotePlayer("", m_server->idef());
- newplayer = true;
- }
-
- for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
- //// Open file and deserialize
- std::ifstream is(path.c_str(), std::ios_base::binary);
- if (!is.good())
- continue;
-
- player->deSerialize(is, path, sao);
- is.close();
-
- if (player->getName() == playername) {
- found = true;
- break;
+ PlayerSAO *playersao = new PlayerSAO(this, player, peer_id, is_singleplayer);
+ // Create player if it doesn't exist
+ if (!m_player_database->loadPlayer(player, playersao)) {
+ *new_player = true;
+ // Set player position
+ infostream << "Server: Finding spawn place for player \""
+ << player->getName() << "\"" << std::endl;
+ playersao->setBasePosition(m_server->findSpawnPos());
+
+ // Make sure the player is saved
+ player->setModified(true);
+ } else {
+ // If the player exists, ensure that they respawn inside legal bounds
+ // This fixes an assert crash when the player can't be added
+ // to the environment
+ if (objectpos_over_limit(playersao->getBasePosition())) {
+ actionstream << "Respawn position for player \""
+ << player->getName() << "\" outside limits, resetting" << std::endl;
+ playersao->setBasePosition(m_server->findSpawnPos());
}
-
- path = players_path + playername + itos(i);
}
- if (!found) {
- infostream << "Player file for player " << playername
- << " not found" << std::endl;
- if (newplayer)
- delete player;
+ // Add player to environment
+ addPlayer(player);
- return NULL;
- }
+ /* Clean up old HUD elements from previous sessions */
+ player->clearHud();
- if (newplayer) {
- addPlayer(player);
- }
- player->setModified(false);
- return player;
+ /* Add object to environment */
+ addActiveObject(playersao);
+
+ return playersao;
}
void ServerEnvironment::saveMeta()
@@ -2173,3 +2209,111 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete)
m_active_objects.erase(*i);
}
}
+
+PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
+ const std::string &savedir, const Settings &conf)
+{
+
+ if (name == "sqlite3")
+ return new PlayerDatabaseSQLite3(savedir);
+ else if (name == "dummy")
+ return new Database_Dummy();
+#if USE_POSTGRESQL
+ else if (name == "postgresql") {
+ std::string connect_string = "";
+ conf.getNoEx("pgsql_player_connection", connect_string);
+ return new PlayerDatabasePostgreSQL(connect_string);
+ }
+#endif
+ else if (name == "files")
+ return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
+ else
+ throw BaseException(std::string("Database backend ") + name + " not supported.");
+}
+
+bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
+ const Settings &cmd_args)
+{
+ std::string migrate_to = cmd_args.get("migrate-players");
+ Settings world_mt;
+ std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
+ if (!world_mt.readConfigFile(world_mt_path.c_str())) {
+ errorstream << "Cannot read world.mt!" << std::endl;
+ return false;
+ }
+
+ if (!world_mt.exists("player_backend")) {
+ errorstream << "Please specify your current backend in world.mt:"
+ << std::endl
+ << " player_backend = {files|sqlite3|postgresql}"
+ << std::endl;
+ return false;
+ }
+
+ std::string backend = world_mt.get("player_backend");
+ if (backend == migrate_to) {
+ errorstream << "Cannot migrate: new backend is same"
+ << " as the old one" << std::endl;
+ return false;
+ }
+
+ const std::string players_backup_path = game_params.world_path + DIR_DELIM
+ + "players.bak";
+
+ if (backend == "files") {
+ // Create backup directory
+ fs::CreateDir(players_backup_path);
+ }
+
+ try {
+ PlayerDatabase *srcdb = ServerEnvironment::openPlayerDatabase(backend,
+ game_params.world_path, world_mt);
+ PlayerDatabase *dstdb = ServerEnvironment::openPlayerDatabase(migrate_to,
+ game_params.world_path, world_mt);
+
+ std::vector<std::string> player_list;
+ srcdb->listPlayers(player_list);
+ for (std::vector<std::string>::const_iterator it = player_list.begin();
+ it != player_list.end(); ++it) {
+ actionstream << "Migrating player " << it->c_str() << std::endl;
+ RemotePlayer player(it->c_str(), NULL);
+ PlayerSAO playerSAO(NULL, &player, 15000, false);
+
+ srcdb->loadPlayer(&player, &playerSAO);
+
+ playerSAO.finalize(&player, std::set<std::string>());
+ player.setPlayerSAO(&playerSAO);
+
+ dstdb->savePlayer(&player);
+
+ // For files source, move player files to backup dir
+ if (backend == "files") {
+ fs::Rename(
+ game_params.world_path + DIR_DELIM + "players" + DIR_DELIM + (*it),
+ players_backup_path + DIR_DELIM + (*it));
+ }
+ }
+
+ actionstream << "Successfully migrated " << player_list.size() << " players"
+ << std::endl;
+ world_mt.set("player_backend", migrate_to);
+ if (!world_mt.updateConfigFile(world_mt_path.c_str()))
+ errorstream << "Failed to update world.mt!" << std::endl;
+ else
+ actionstream << "world.mt updated" << std::endl;
+
+ // When migration is finished from file backend, remove players directory if empty
+ if (backend == "files") {
+ fs::DeleteSingleFileOrEmptyDirectory(game_params.world_path + DIR_DELIM
+ + "players");
+ }
+
+ delete srcdb;
+ delete dstdb;
+
+ } catch (BaseException &e) {
+ errorstream << "An error occured during migration: " << e.what() << std::endl;
+ return false;
+ }
+ return true;
+}
diff --git a/src/serverenvironment.h b/src/serverenvironment.h
index 99110542a..0e31aa41a 100644
--- a/src/serverenvironment.h
+++ b/src/serverenvironment.h
@@ -27,7 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class IGameDef;
class ServerMap;
+struct GameParams;
class RemotePlayer;
+class PlayerDatabase;
class PlayerSAO;
class ServerEnvironment;
class ActiveBlockModifier;
@@ -217,9 +219,11 @@ public:
// Save players
void saveLoadedPlayers();
void savePlayer(RemotePlayer *player);
- RemotePlayer *loadPlayer(const std::string &playername, PlayerSAO *sao);
+ PlayerSAO *loadPlayer(RemotePlayer *player, bool *new_player, u16 peer_id,
+ bool is_singleplayer);
void addPlayer(RemotePlayer *player);
void removePlayer(RemotePlayer *player);
+ bool removePlayerFromDatabase(const std::string &name);
/*
Save and load time of day and game timer
@@ -334,8 +338,13 @@ public:
RemotePlayer *getPlayer(const u16 peer_id);
RemotePlayer *getPlayer(const char* name);
+
+ static bool migratePlayersDatabase(const GameParams &game_params,
+ const Settings &cmd_args);
private:
+ static PlayerDatabase *openPlayerDatabase(const std::string &name,
+ const std::string &savedir, const Settings &conf);
/*
Internal ActiveObject interface
-------------------------------------------
@@ -419,6 +428,8 @@ private:
// peer_ids in here should be unique, except that there may be many 0s
std::vector<RemotePlayer*> m_players;
+ PlayerDatabase *m_player_database;
+
// Particles
IntervalLimiter m_particle_management_interval;
UNORDERED_MAP<u32, float> m_particle_spawners;
diff --git a/src/unittest/test_player.cpp b/src/unittest/test_player.cpp
index b639878b2..e2b1cd855 100644
--- a/src/unittest/test_player.cpp
+++ b/src/unittest/test_player.cpp
@@ -31,59 +31,10 @@ public:
const char *getName() { return "TestPlayer"; }
void runTests(IGameDef *gamedef);
-
- void testSave(IGameDef *gamedef);
- void testLoad(IGameDef *gamedef);
};
static TestPlayer g_test_instance;
void TestPlayer::runTests(IGameDef *gamedef)
{
- TEST(testSave, gamedef);
- TEST(testLoad, gamedef);
-}
-
-void TestPlayer::testSave(IGameDef *gamedef)
-{
- RemotePlayer rplayer("testplayer_save", gamedef->idef());
- PlayerSAO sao(NULL, 1, false);
- sao.initialize(&rplayer, std::set<std::string>());
- rplayer.setPlayerSAO(&sao);
- sao.setBreath(10, false);
- sao.setHPRaw(8);
- sao.setYaw(0.1f);
- sao.setPitch(0.6f);
- sao.setBasePosition(v3f(450.2f, -15.7f, 68.1f));
- rplayer.save(".", gamedef);
- UASSERT(fs::PathExists("testplayer_save"));
-}
-
-void TestPlayer::testLoad(IGameDef *gamedef)
-{
- RemotePlayer rplayer("testplayer_load", gamedef->idef());
- PlayerSAO sao(NULL, 1, false);
- sao.initialize(&rplayer, std::set<std::string>());
- rplayer.setPlayerSAO(&sao);
- sao.setBreath(10, false);
- sao.setHPRaw(8);
- sao.setYaw(0.1f);
- sao.setPitch(0.6f);
- sao.setBasePosition(v3f(450.2f, -15.7f, 68.1f));
- rplayer.save(".", gamedef);
- UASSERT(fs::PathExists("testplayer_load"));
-
- RemotePlayer rplayer_load("testplayer_load", gamedef->idef());
- PlayerSAO sao_load(NULL, 2, false);
- std::ifstream is("testplayer_load", std::ios_base::binary);
- UASSERT(is.good());
- rplayer_load.deSerialize(is, "testplayer_load", &sao_load);
- is.close();
-
- UASSERT(strcmp(rplayer_load.getName(), "testplayer_load") == 0);
- UASSERT(sao_load.getBreath() == 10);
- UASSERT(sao_load.getHP() == 8);
- UASSERT(sao_load.getYaw() == 0.1f);
- UASSERT(sao_load.getPitch() == 0.6f);
- UASSERT(sao_load.getBasePosition() == v3f(450.2f, -15.7f, 68.1f));
}