Skip to content

Commit

Permalink
Player data to Database (#5475)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nerzhul committed Apr 23, 2017
1 parent dda171d commit 29ab20c
Show file tree
Hide file tree
Showing 31 changed files with 1,547 additions and 370 deletions.
1 change: 1 addition & 0 deletions build/android/jni/Android.mk
Expand Up @@ -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 \
Expand Down
25 changes: 25 additions & 0 deletions builtin/game/chatcommands.lua
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions doc/lua_api.txt
Expand Up @@ -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("")`)
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/client.cpp
Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions src/client.h
Expand Up @@ -49,7 +49,7 @@ class ClientMediaDownloader;
struct MapDrawControl;
class MtEventManager;
struct PointedThing;
class Database;
class MapDatabase;
class Minimap;
struct MinimapMapblock;
class Camera;
Expand Down Expand Up @@ -645,7 +645,7 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
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;

Expand Down
7 changes: 4 additions & 3 deletions src/content_sao.cpp
Expand Up @@ -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),
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/content_sao.h
Expand Up @@ -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; }
Expand Down Expand Up @@ -349,7 +349,7 @@ class PlayerSAO : public UnitSAO
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;
Expand Down
9 changes: 8 additions & 1 deletion src/database-dummy.h
Expand Up @@ -25,14 +25,21 @@ 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);
void loadBlock(const v3s16 &pos, std::string *block);
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;
};
Expand Down
179 changes: 179 additions & 0 deletions 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());
}
}
46 changes: 46 additions & 0 deletions 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
4 changes: 3 additions & 1 deletion src/database-leveldb.h
Expand Up @@ -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);
Expand All @@ -39,6 +39,8 @@ class Database_LevelDB : public Database
bool deleteBlock(const v3s16 &pos);
void listAllLoadableBlocks(std::vector<v3s16> &dst);

void beginSave() {}
void endSave() {}
private:
leveldb::DB *m_database;
};
Expand Down

0 comments on commit 29ab20c

Please sign in to comment.