1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Move files to subdirectories (#6599)

* Move files around
This commit is contained in:
Vitaliy 2017-11-09 01:56:20 +03:00 committed by Loïc Blot
parent fc9747eb4b
commit 20a85d76d9
118 changed files with 236 additions and 221 deletions

View file

@ -0,0 +1,10 @@
set(database_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/database.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-dummy.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-files.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-leveldb.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-postgresql.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-redis.cpp
${CMAKE_CURRENT_SOURCE_DIR}/database-sqlite3.cpp
PARENT_SCOPE
)

View file

@ -0,0 +1,59 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
/*
Dummy database class
*/
#include "database-dummy.h"
bool Database_Dummy::saveBlock(const v3s16 &pos, const std::string &data)
{
m_database[getBlockAsInteger(pos)] = data;
return true;
}
void Database_Dummy::loadBlock(const v3s16 &pos, std::string *block)
{
s64 i = getBlockAsInteger(pos);
auto it = m_database.find(i);
if (it == m_database.end()) {
*block = "";
return;
}
*block = it->second;
}
bool Database_Dummy::deleteBlock(const v3s16 &pos)
{
m_database.erase(getBlockAsInteger(pos));
return true;
}
void Database_Dummy::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
dst.reserve(m_database.size());
for (std::map<s64, std::string>::const_iterator x = m_database.begin();
x != m_database.end(); ++x) {
dst.push_back(getIntegerAsBlock(x->first));
}
}

View file

@ -0,0 +1,45 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
#pragma once
#include <map>
#include <string>
#include "database.h"
#include "irrlichttypes.h"
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> &res) {}
void beginSave() {}
void endSave() {}
private:
std::map<s64, std::string> m_database;
};

View file

@ -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.emplace_back(player.getName());
}
}

View file

@ -0,0 +1,43 @@
/*
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.
*/
#pragma once
// !!! 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() = default;
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;
};

View file

@ -0,0 +1,101 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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 "config.h"
#if USE_LEVELDB
#include "database-leveldb.h"
#include "log.h"
#include "filesys.h"
#include "exceptions.h"
#include "util/string.h"
#include "leveldb/db.h"
#define ENSURE_STATUS_OK(s) \
if (!(s).ok()) { \
throw DatabaseException(std::string("LevelDB error: ") + \
(s).ToString()); \
}
Database_LevelDB::Database_LevelDB(const std::string &savedir)
{
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options,
savedir + DIR_DELIM + "map.db", &m_database);
ENSURE_STATUS_OK(status);
}
Database_LevelDB::~Database_LevelDB()
{
delete m_database;
}
bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data)
{
leveldb::Status status = m_database->Put(leveldb::WriteOptions(),
i64tos(getBlockAsInteger(pos)), data);
if (!status.ok()) {
warningstream << "saveBlock: LevelDB error saving block "
<< PP(pos) << ": " << status.ToString() << std::endl;
return false;
}
return true;
}
void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block)
{
std::string datastr;
leveldb::Status status = m_database->Get(leveldb::ReadOptions(),
i64tos(getBlockAsInteger(pos)), &datastr);
*block = (status.ok()) ? datastr : "";
}
bool Database_LevelDB::deleteBlock(const v3s16 &pos)
{
leveldb::Status status = m_database->Delete(leveldb::WriteOptions(),
i64tos(getBlockAsInteger(pos)));
if (!status.ok()) {
warningstream << "deleteBlock: LevelDB error deleting block "
<< PP(pos) << ": " << status.ToString() << std::endl;
return false;
}
return true;
}
void Database_LevelDB::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) {
dst.push_back(getIntegerAsBlock(stoi64(it->key().ToString())));
}
ENSURE_STATUS_OK(it->status()); // Check for any errors found during the scan
delete it;
}
#endif // USE_LEVELDB

View file

@ -0,0 +1,48 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
#pragma once
#include "config.h"
#if USE_LEVELDB
#include <string>
#include "database.h"
#include "leveldb/db.h"
class Database_LevelDB : public MapDatabase
{
public:
Database_LevelDB(const std::string &savedir);
~Database_LevelDB();
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() {}
void endSave() {}
private:
leveldb::DB *m_database;
};
#endif // USE_LEVELDB

View file

@ -0,0 +1,631 @@
/*
Copyright (C) 2016 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 "config.h"
#if USE_POSTGRESQL
#include "database-postgresql.h"
#ifdef _WIN32
// Without this some of the network functions are not found on mingw
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include <windows.h>
#include <winsock2.h>
#else
#include <netinet/in.h>
#endif
#include "debug.h"
#include "exceptions.h"
#include "settings.h"
#include "content_sao.h"
#include "remoteplayer.h"
Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) :
m_connect_string(connect_string)
{
if (m_connect_string.empty()) {
throw SettingNotFoundException(
"Set pgsql_connection string in world.mt to "
"use the postgresql backend\n"
"Notes:\n"
"pgsql_connection has the following form: \n"
"\tpgsql_connection = host=127.0.0.1 port=5432 user=mt_user "
"password=mt_password dbname=minetest_world\n"
"mt_user should have CREATE TABLE, INSERT, SELECT, UPDATE and "
"DELETE rights on the database.\n"
"Don't create mt_user as a SUPERUSER!");
}
}
Database_PostgreSQL::~Database_PostgreSQL()
{
PQfinish(m_conn);
}
void Database_PostgreSQL::connectToDatabase()
{
m_conn = PQconnectdb(m_connect_string.c_str());
if (PQstatus(m_conn) != CONNECTION_OK) {
throw DatabaseException(std::string(
"PostgreSQL database error: ") +
PQerrorMessage(m_conn));
}
m_pgversion = PQserverVersion(m_conn);
/*
* We are using UPSERT feature from PostgreSQL 9.5
* to have the better performance where possible.
*/
if (m_pgversion < 90500) {
warningstream << "Your PostgreSQL server lacks UPSERT "
<< "support. Use version 9.5 or better if possible."
<< std::endl;
}
infostream << "PostgreSQL Database: Version " << m_pgversion
<< " Connection made." << std::endl;
createDatabase();
initStatements();
}
void Database_PostgreSQL::verifyDatabase()
{
if (PQstatus(m_conn) == CONNECTION_OK)
return;
PQreset(m_conn);
ping();
}
void Database_PostgreSQL::ping()
{
if (PQping(m_connect_string.c_str()) != PQPING_OK) {
throw DatabaseException(std::string(
"PostgreSQL database error: ") +
PQerrorMessage(m_conn));
}
}
bool Database_PostgreSQL::initialized() const
{
return (PQstatus(m_conn) == CONNECTION_OK);
}
PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
{
ExecStatusType statusType = PQresultStatus(result);
switch (statusType) {
case PGRES_COMMAND_OK:
case PGRES_TUPLES_OK:
break;
case PGRES_FATAL_ERROR:
default:
throw DatabaseException(
std::string("PostgreSQL database error: ") +
PQresultErrorMessage(result));
}
if (clear)
PQclear(result);
return result;
}
void Database_PostgreSQL::createTableIfNotExists(const std::string &table_name,
const std::string &definition)
{
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)) {
checkResults(PQexec(m_conn, definition.c_str()));
}
PQclear(result);
}
void Database_PostgreSQL::beginSave()
{
verifyDatabase();
checkResults(PQexec(m_conn, "BEGIN;"));
}
void Database_PostgreSQL::endSave()
{
checkResults(PQexec(m_conn, "COMMIT;"));
}
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 0xFFFFFFFF (== " << data.size()
<< ")" << std::endl;
return false;
}
verifyDatabase();
s32 x, y, z;
x = htonl(pos.X);
y = htonl(pos.Y);
z = htonl(pos.Z);
const void *args[] = { &x, &y, &z, data.c_str() };
const int argLen[] = {
sizeof(x), sizeof(y), sizeof(z), (int)data.size()
};
const int argFmt[] = { 1, 1, 1, 1 };
if (getPGVersion() < 90500) {
execPrepared("write_block_update", ARRLEN(args), args, argLen, argFmt);
execPrepared("write_block_insert", ARRLEN(args), args, argLen, argFmt);
} else {
execPrepared("write_block", ARRLEN(args), args, argLen, argFmt);
}
return true;
}
void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block)
{
verifyDatabase();
s32 x, y, z;
x = htonl(pos.X);
y = htonl(pos.Y);
z = htonl(pos.Z);
const void *args[] = { &x, &y, &z };
const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
const int argFmt[] = { 1, 1, 1 };
PGresult *results = execPrepared("read_block", ARRLEN(args), args,
argLen, argFmt, false);
*block = "";
if (PQntuples(results))
*block = std::string(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0));
PQclear(results);
}
bool MapDatabasePostgreSQL::deleteBlock(const v3s16 &pos)
{
verifyDatabase();
s32 x, y, z;
x = htonl(pos.X);
y = htonl(pos.Y);
z = htonl(pos.Z);
const void *args[] = { &x, &y, &z };
const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
const int argFmt[] = { 1, 1, 1 };
execPrepared("delete_block", ARRLEN(args), args, argLen, argFmt);
return true;
}
void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
verifyDatabase();
PGresult *results = execPrepared("list_all_loadable_blocks", 0,
NULL, NULL, NULL, false, false);
int numrows = PQntuples(results);
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];
const std::string &name = list->getName();
std::string 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 (const auto &attr : attrs) {
const char *meta_values[] = {
player->getName(),
attr.first.c_str(),
attr.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->changeItem(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.emplace_back(PQgetvalue(results, row, 0));
PQclear(results);
}
#endif // USE_POSTGRESQL

View file

@ -0,0 +1,146 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
#pragma once
#include <string>
#include <libpq-fe.h>
#include "database.h"
#include "util/basic_macros.h"
class Settings;
class Database_PostgreSQL: public Database
{
public:
Database_PostgreSQL(const std::string &connect_string);
~Database_PostgreSQL();
void beginSave();
void endSave();
bool initialized() const;
protected:
// Conversion helpers
inline int pg_to_int(PGresult *res, int row, int col)
{
return atoi(PQgetvalue(res, row, col));
}
inline u32 pg_to_uint(PGresult *res, int row, int col)
{
return (u32) atoi(PQgetvalue(res, row, col));
}
inline float pg_to_float(PGresult *res, int row, int col)
{
return (float) atof(PQgetvalue(res, row, col));
}
inline v3s16 pg_to_v3s16(PGresult *res, int row, int col)
{
return v3s16(
pg_to_int(res, row, col),
pg_to_int(res, row, col + 1),
pg_to_int(res, row, col + 2)
);
}
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 = nullptr;
int m_pgversion = 0;
};
class MapDatabasePostgreSQL : private Database_PostgreSQL, public MapDatabase
{
public:
MapDatabasePostgreSQL(const std::string &connect_string);
virtual ~MapDatabasePostgreSQL() = default;
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() = default;
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);
};

View file

@ -0,0 +1,203 @@
/*
Minetest
Copyright (C) 2014 celeron55, Perttu Ahola <celeron55@gmail.com>
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 "config.h"
#if USE_REDIS
#include "database-redis.h"
#include "settings.h"
#include "log.h"
#include "exceptions.h"
#include "util/string.h"
#include <hiredis.h>
#include <cassert>
Database_Redis::Database_Redis(Settings &conf)
{
std::string tmp;
try {
tmp = conf.get("redis_address");
hash = conf.get("redis_hash");
} catch (SettingNotFoundException &) {
throw SettingNotFoundException("Set redis_address and "
"redis_hash in world.mt to use the redis backend");
}
const char *addr = tmp.c_str();
int port = conf.exists("redis_port") ? conf.getU16("redis_port") : 6379;
// if redis_address contains '/' assume unix socket, else hostname/ip
ctx = tmp.find('/') != std::string::npos ? redisConnectUnix(addr) : redisConnect(addr, port);
if (!ctx) {
throw DatabaseException("Cannot allocate redis context");
} else if (ctx->err) {
std::string err = std::string("Connection error: ") + ctx->errstr;
redisFree(ctx);
throw DatabaseException(err);
}
if (conf.exists("redis_password")) {
tmp = conf.get("redis_password");
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "AUTH %s", tmp.c_str()));
if (!reply)
throw DatabaseException("Redis authentication failed");
if (reply->type == REDIS_REPLY_ERROR) {
std::string err = "Redis authentication failed: " + std::string(reply->str, reply->len);
freeReplyObject(reply);
throw DatabaseException(err);
}
freeReplyObject(reply);
}
}
Database_Redis::~Database_Redis()
{
redisFree(ctx);
}
void Database_Redis::beginSave() {
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "MULTI"));
if (!reply) {
throw DatabaseException(std::string(
"Redis command 'MULTI' failed: ") + ctx->errstr);
}
freeReplyObject(reply);
}
void Database_Redis::endSave() {
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "EXEC"));
if (!reply) {
throw DatabaseException(std::string(
"Redis command 'EXEC' failed: ") + ctx->errstr);
}
freeReplyObject(reply);
}
bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data)
{
std::string tmp = i64tos(getBlockAsInteger(pos));
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HSET %s %s %b",
hash.c_str(), tmp.c_str(), data.c_str(), data.size()));
if (!reply) {
warningstream << "saveBlock: redis command 'HSET' failed on "
"block " << PP(pos) << ": " << ctx->errstr << std::endl;
freeReplyObject(reply);
return false;
}
if (reply->type == REDIS_REPLY_ERROR) {
warningstream << "saveBlock: saving block " << PP(pos)
<< " failed: " << std::string(reply->str, reply->len) << std::endl;
freeReplyObject(reply);
return false;
}
freeReplyObject(reply);
return true;
}
void Database_Redis::loadBlock(const v3s16 &pos, std::string *block)
{
std::string tmp = i64tos(getBlockAsInteger(pos));
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
"HGET %s %s", hash.c_str(), tmp.c_str()));
if (!reply) {
throw DatabaseException(std::string(
"Redis command 'HGET %s %s' failed: ") + ctx->errstr);
}
switch (reply->type) {
case REDIS_REPLY_STRING: {
*block = std::string(reply->str, reply->len);
// std::string copies the memory so this won't cause any problems
freeReplyObject(reply);
return;
}
case REDIS_REPLY_ERROR: {
std::string errstr(reply->str, reply->len);
freeReplyObject(reply);
errorstream << "loadBlock: loading block " << PP(pos)
<< " failed: " << errstr << std::endl;
throw DatabaseException(std::string(
"Redis command 'HGET %s %s' errored: ") + errstr);
}
case REDIS_REPLY_NIL: {
*block = "";
// block not found in database
freeReplyObject(reply);
return;
}
}
errorstream << "loadBlock: loading block " << PP(pos)
<< " returned invalid reply type " << reply->type
<< ": " << std::string(reply->str, reply->len) << std::endl;
freeReplyObject(reply);
throw DatabaseException(std::string(
"Redis command 'HGET %s %s' gave invalid reply."));
}
bool Database_Redis::deleteBlock(const v3s16 &pos)
{
std::string tmp = i64tos(getBlockAsInteger(pos));
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
"HDEL %s %s", hash.c_str(), tmp.c_str()));
if (!reply) {
throw DatabaseException(std::string(
"Redis command 'HDEL %s %s' failed: ") + ctx->errstr);
} else if (reply->type == REDIS_REPLY_ERROR) {
warningstream << "deleteBlock: deleting block " << PP(pos)
<< " failed: " << std::string(reply->str, reply->len) << std::endl;
freeReplyObject(reply);
return false;
}
freeReplyObject(reply);
return true;
}
void Database_Redis::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HKEYS %s", hash.c_str()));
if (!reply) {
throw DatabaseException(std::string(
"Redis command 'HKEYS %s' failed: ") + ctx->errstr);
}
switch (reply->type) {
case REDIS_REPLY_ARRAY:
dst.reserve(reply->elements);
for (size_t i = 0; i < reply->elements; i++) {
assert(reply->element[i]->type == REDIS_REPLY_STRING);
dst.push_back(getIntegerAsBlock(stoi64(reply->element[i]->str)));
}
break;
case REDIS_REPLY_ERROR:
throw DatabaseException(std::string(
"Failed to get keys from database: ") +
std::string(reply->str, reply->len));
}
freeReplyObject(reply);
}
#endif // USE_REDIS

View file

@ -0,0 +1,51 @@
/*
Minetest
Copyright (C) 2014 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
#pragma once
#include "config.h"
#if USE_REDIS
#include <hiredis.h>
#include <string>
#include "database.h"
class Settings;
class Database_Redis : public MapDatabase
{
public:
Database_Redis(Settings &conf);
~Database_Redis();
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);
private:
redisContext *ctx = nullptr;
std::string hash = "";
};
#endif // USE_REDIS

View file

@ -0,0 +1,606 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
/*
SQLite format specification:
blocks:
(PK) INT id
BLOB data
*/
#include "database-sqlite3.h"
#include "log.h"
#include "filesys.h"
#include "exceptions.h"
#include "settings.h"
#include "porting.h"
#include "util/string.h"
#include "content_sao.h"
#include "remoteplayer.h"
#include <cassert>
// When to print messages when the database is being held locked by another process
// Note: I've seen occasional delays of over 250ms while running minetestmapper.
#define BUSY_INFO_TRESHOLD 100 // Print first informational message after 100ms.
#define BUSY_WARNING_TRESHOLD 250 // Print warning message after 250ms. Lag is increased.
#define BUSY_ERROR_TRESHOLD 1000 // Print error message after 1000ms. Significant lag.
#define BUSY_FATAL_TRESHOLD 3000 // Allow SQLITE_BUSY to be returned, which will cause a minetest crash.
#define BUSY_ERROR_INTERVAL 10000 // Safety net: report again every 10 seconds
#define SQLRES(s, r, m) \
if ((s) != (r)) { \
throw DatabaseException(std::string(m) + ": " +\
sqlite3_errmsg(m_database)); \
}
#define SQLOK(s, m) SQLRES(s, SQLITE_OK, m)
#define PREPARE_STATEMENT(name, query) \
SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL),\
"Failed to prepare query '" query "'")
#define SQLOK_ERRSTREAM(s, m) \
if ((s) != SQLITE_OK) { \
errorstream << (m) << ": " \
<< sqlite3_errmsg(m_database) << std::endl; \
}
#define FINALIZE_STATEMENT(statement) SQLOK_ERRSTREAM(sqlite3_finalize(statement), \
"Failed to finalize " #statement)
int Database_SQLite3::busyHandler(void *data, int count)
{
s64 &first_time = reinterpret_cast<s64 *>(data)[0];
s64 &prev_time = reinterpret_cast<s64 *>(data)[1];
s64 cur_time = porting::getTimeMs();
if (count == 0) {
first_time = cur_time;
prev_time = first_time;
} else {
while (cur_time < prev_time)
cur_time += s64(1)<<32;
}
if (cur_time - first_time < BUSY_INFO_TRESHOLD) {
; // do nothing
} else if (cur_time - first_time >= BUSY_INFO_TRESHOLD &&
prev_time - first_time < BUSY_INFO_TRESHOLD) {
infostream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms." << std::endl;
} else if (cur_time - first_time >= BUSY_WARNING_TRESHOLD &&
prev_time - first_time < BUSY_WARNING_TRESHOLD) {
warningstream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms." << std::endl;
} else if (cur_time - first_time >= BUSY_ERROR_TRESHOLD &&
prev_time - first_time < BUSY_ERROR_TRESHOLD) {
errorstream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms; this causes lag." << std::endl;
} else if (cur_time - first_time >= BUSY_FATAL_TRESHOLD &&
prev_time - first_time < BUSY_FATAL_TRESHOLD) {
errorstream << "SQLite3 database has been locked for "
<< cur_time - first_time << " ms - giving up!" << std::endl;
} else if ((cur_time - first_time) / BUSY_ERROR_INTERVAL !=
(prev_time - first_time) / BUSY_ERROR_INTERVAL) {
// Safety net: keep reporting every BUSY_ERROR_INTERVAL
errorstream << "SQLite3 database has been locked for "
<< (cur_time - first_time) / 1000 << " seconds!" << std::endl;
}
prev_time = cur_time;
// Make sqlite transaction fail if delay exceeds BUSY_FATAL_TRESHOLD
return cur_time - first_time < BUSY_FATAL_TRESHOLD;
}
Database_SQLite3::Database_SQLite3(const std::string &savedir, const std::string &dbname) :
m_savedir(savedir),
m_dbname(dbname)
{
}
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()
{
verifyDatabase();
SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE,
"Failed to commit SQLite3 transaction");
sqlite3_reset(m_stmt_end);
}
void Database_SQLite3::openDatabase()
{
if (m_database) return;
std::string dbp = m_savedir + DIR_DELIM + m_dbname + ".sqlite";
// Open the database connection
if (!fs::CreateAllDirs(m_savedir)) {
infostream << "Database_SQLite3: Failed to create directory \""
<< m_savedir << "\"" << std::endl;
throw FileNotGoodException("Failed to create database "
"save directory");
}
bool needs_create = !fs::PathExists(dbp);
SQLOK(sqlite3_open_v2(dbp.c_str(), &m_database,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL),
std::string("Failed to open SQLite3 database file ") + dbp);
SQLOK(sqlite3_busy_handler(m_database, Database_SQLite3::busyHandler,
m_busy_handler_data), "Failed to set SQLite3 busy handler");
if (needs_create) {
createDatabase();
}
std::string query_str = std::string("PRAGMA synchronous = ")
+ 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()
{
if (m_initialized) return;
openDatabase();
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()
{
}
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 (?, ?)");
#else
PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
#endif
PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?");
PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`");
verbosestream << "ServerMap: SQLite3 database opened." << std::endl;
}
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 MapDatabaseSQLite3::deleteBlock(const v3s16 &pos)
{
verifyDatabase();
bindPos(m_stmt_delete, pos);
bool good = sqlite3_step(m_stmt_delete) == SQLITE_DONE;
sqlite3_reset(m_stmt_delete);
if (!good) {
warningstream << "deleteBlock: Block failed to delete "
<< PP(pos) << ": " << sqlite3_errmsg(m_database) << std::endl;
}
return good;
}
bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data)
{
verifyDatabase();
#ifdef __ANDROID__
/**
* Note: For some unknown reason SQLite3 fails to REPLACE blocks on Android,
* deleting them and then inserting works.
*/
bindPos(m_stmt_read, pos);
if (sqlite3_step(m_stmt_read) == SQLITE_ROW) {
deleteBlock(pos);
}
sqlite3_reset(m_stmt_read);
#endif
bindPos(m_stmt_write, pos);
SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL),
"Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
SQLRES(sqlite3_step(m_stmt_write), SQLITE_DONE, "Failed to save block")
sqlite3_reset(m_stmt_write);
return true;
}
void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block)
{
verifyDatabase();
bindPos(m_stmt_read, pos);
if (sqlite3_step(m_stmt_read) != SQLITE_ROW) {
sqlite3_reset(m_stmt_read);
return;
}
const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0);
size_t len = sqlite3_column_bytes(m_stmt_read, 0);
*block = (data) ? std::string(data, len) : "";
sqlite3_step(m_stmt_read);
// We should never get more than 1 row, so ok to reset
sqlite3_reset(m_stmt_read);
}
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()
{
}
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 `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 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 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;
}
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);
}
// 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 (const auto &attr : attrs) {
str_to_sqlite(m_stmt_player_metadata_add, 1, player->getName());
str_to_sqlite(m_stmt_player_metadata_add, 2, attr.first);
str_to_sqlite(m_stmt_player_metadata_add, 3, attr.second);
sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_add), SQLITE_DONE);
sqlite3_reset(m_stmt_player_metadata_add);
}
endSave();
}
bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
{
verifyDatabase();
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->changeItem(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);
}

View file

@ -0,0 +1,193 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
#pragma once
#include <cstring>
#include <string>
#include "database.h"
#include "exceptions.h"
extern "C" {
#include "sqlite3.h"
}
class Database_SQLite3 : public Database
{
public:
virtual ~Database_SQLite3();
void beginSave();
void endSave();
bool initialized() const { return m_initialized; }
protected:
Database_SQLite3(const std::string &savedir, const std::string &dbname);
// Open and initialize the database if needed
void verifyDatabase();
// 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 = nullptr;
private:
// Open the database
void openDatabase();
bool m_initialized = false;
std::string m_savedir = "";
std::string m_dbname = "";
sqlite3_stmt *m_stmt_begin = nullptr;
sqlite3_stmt *m_stmt_end = nullptr;
s64 m_busy_handler_data[2];
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 = nullptr;
sqlite3_stmt *m_stmt_write = nullptr;
sqlite3_stmt *m_stmt_list = nullptr;
sqlite3_stmt *m_stmt_delete = nullptr;
};
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 = nullptr;
sqlite3_stmt *m_stmt_player_add = nullptr;
sqlite3_stmt *m_stmt_player_update = nullptr;
sqlite3_stmt *m_stmt_player_remove = nullptr;
sqlite3_stmt *m_stmt_player_list = nullptr;
sqlite3_stmt *m_stmt_player_load_inventory = nullptr;
sqlite3_stmt *m_stmt_player_load_inventory_items = nullptr;
sqlite3_stmt *m_stmt_player_add_inventory = nullptr;
sqlite3_stmt *m_stmt_player_add_inventory_items = nullptr;
sqlite3_stmt *m_stmt_player_remove_inventory = nullptr;
sqlite3_stmt *m_stmt_player_remove_inventory_items = nullptr;
sqlite3_stmt *m_stmt_player_metadata_load = nullptr;
sqlite3_stmt *m_stmt_player_metadata_remove = nullptr;
sqlite3_stmt *m_stmt_player_metadata_add = nullptr;
};

69
src/database/database.cpp Normal file
View file

@ -0,0 +1,69 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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 "database.h"
#include "irrlichttypes.h"
/****************
* Black magic! *
****************
* The position hashing is very messed up.
* It's a lot more complicated than it looks.
*/
static inline s16 unsigned_to_signed(u16 i, u16 max_positive)
{
if (i < max_positive) {
return i;
}
return i - (max_positive * 2);
}
// Modulo of a negative number does not work consistently in C
static inline s64 pythonmodulo(s64 i, s16 mod)
{
if (i >= 0) {
return i % mod;
}
return mod - ((-i) % mod);
}
s64 MapDatabase::getBlockAsInteger(const v3s16 &pos)
{
return (u64) pos.Z * 0x1000000 +
(u64) pos.Y * 0x1000 +
(u64) pos.X;
}
v3s16 MapDatabase::getIntegerAsBlock(s64 i)
{
v3s16 pos;
pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
i = (i - pos.X) / 4096;
pos.Y = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
i = (i - pos.Y) / 4096;
pos.Z = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
return pos;
}

63
src/database/database.h Normal file
View file

@ -0,0 +1,63 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
#pragma once
#include <string>
#include <vector>
#include "irr_v3d.h"
#include "irrlichttypes.h"
#include "util/basic_macros.h"
class Database
{
public:
virtual void beginSave() = 0;
virtual void endSave() = 0;
virtual bool initialized() const { return true; }
};
class MapDatabase : public Database
{
public:
virtual ~MapDatabase() = default;
virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0;
virtual void loadBlock(const v3s16 &pos, std::string *block) = 0;
virtual bool deleteBlock(const v3s16 &pos) = 0;
static s64 getBlockAsInteger(const v3s16 &pos);
static v3s16 getIntegerAsBlock(s64 i);
virtual void listAllLoadableBlocks(std::vector<v3s16> &dst) = 0;
};
class PlayerSAO;
class RemotePlayer;
class PlayerDatabase
{
public:
virtual ~PlayerDatabase() = default;
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;
};