#include "config.h"
#include "version.h"
#include "drawscene.h"
-#include "subgame.h"
-#include "server.h"
-#include "database.h"
#include "database-sqlite3.h"
+#include "serialization.h"
extern gui::IGUIEnvironment* guienv;
m_time_of_day_update_timer(0),
m_recommended_send_interval(0.1),
m_removed_sounds_check_timer(0),
- m_state(LC_Created)
+ m_state(LC_Created),
+ m_localdb(NULL)
{
- /*
- Add local player
- */
- {
- Player *player = new LocalPlayer(this, playername);
+ // Add local player
+ m_env.addPlayer(new LocalPlayer(this, playername));
- m_env.addPlayer(player);
- }
+ m_cache_save_interval = g_settings->getU16("server_map_save_interval");
m_cache_smooth_lighting = g_settings->getBool("smooth_lighting");
m_cache_enable_shaders = g_settings->getBool("enable_shaders");
{
//request all client managed threads to stop
m_mesh_update_thread.Stop();
- if (localdb != NULL) {
- actionstream << "Local map saving ended" << std::endl;
- localdb->endSave();
- delete localserver;
+ // Save local server map
+ if (m_localdb) {
+ infostream << "Local map saving ended." << std::endl;
+ m_localdb->endSave();
}
}
Send(pkt);
}
}
+
+ // Write server map
+ if (m_localdb && m_localdb_save_interval.step(dtime,
+ m_cache_save_interval)) {
+ m_localdb->endSave();
+ m_localdb->beginSave();
+ }
}
bool Client::loadMedia(const std::string &data, const std::string &filename)
const std::string &hostname,
bool is_local_server)
{
- localdb = NULL;
-
- if (!g_settings->getBool("enable_local_map_saving") || is_local_server)
+ if (!g_settings->getBool("enable_local_map_saving") || is_local_server) {
return;
+ }
const std::string world_path = porting::path_user
+ DIR_DELIM + "worlds"
+ DIR_DELIM + "server_"
+ hostname + "_" + to_string(address.getPort());
- SubgameSpec gamespec;
-
- if (!getWorldExists(world_path)) {
- gamespec = findSubgame(g_settings->get("default_game"));
- if (!gamespec.isValid())
- gamespec = findSubgame("minimal");
- } else {
- gamespec = findWorldSubgame(world_path);
- }
-
- if (!gamespec.isValid()) {
- errorstream << "Couldn't find subgame for local map saving." << std::endl;
- return;
- }
+ fs::CreateAllDirs(world_path);
- localserver = new Server(world_path, gamespec, false, false);
- localdb = new Database_SQLite3(&(ServerMap&)localserver->getMap(), world_path);
- localdb->beginSave();
+ m_localdb = new Database_SQLite3(world_path);
+ m_localdb->beginSave();
actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl;
}
if (image) {
raw_image->copyTo(image);
irr::c8 filename[256];
- snprintf(filename, sizeof(filename), "%s" DIR_DELIM "screenshot_%u.png",
+ snprintf(filename, sizeof(filename),
+ (std::string("%s") + DIR_DELIM + "screenshot_%u.png").c_str(),
g_settings->get("screenshot_path").c_str(),
device->getTimer()->getRealTime());
std::ostringstream sstr;
class MtEventManager;
struct PointedThing;
class Database;
-class Server;
struct QueuedMeshUpdate
{
LocalClientState m_state;
// Used for saving server map to disk client-side
- Database *localdb;
- Server *localserver;
+ Database *m_localdb;
+ IntervalLimiter m_localdb_save_interval;
+ u16 m_cache_save_interval;
// TODO: Add callback to update these when g_settings changes
bool m_cache_smooth_lighting;
*/
/*
-Dummy "database" class
+Dummy database class
*/
-
#include "database-dummy.h"
-#include "map.h"
-#include "mapsector.h"
-#include "mapblock.h"
-#include "serialization.h"
-#include "main.h"
-#include "settings.h"
-#include "log.h"
-
-Database_Dummy::Database_Dummy(ServerMap *map)
-{
- srvmap = map;
-}
-
-int Database_Dummy::Initialized(void)
-{
- return 1;
-}
-
-void Database_Dummy::beginSave() {}
-void Database_Dummy::endSave() {}
-bool Database_Dummy::saveBlock(v3s16 blockpos, std::string &data)
+bool Database_Dummy::saveBlock(const v3s16 &pos, const std::string &data)
{
- m_database[getBlockAsInteger(blockpos)] = data;
+ m_database[getBlockAsInteger(pos)] = data;
return true;
}
-std::string Database_Dummy::loadBlock(v3s16 blockpos)
+std::string Database_Dummy::loadBlock(const v3s16 &pos)
{
- if (m_database.count(getBlockAsInteger(blockpos)))
- return m_database[getBlockAsInteger(blockpos)];
+ s64 i = getBlockAsInteger(pos);
+ if (m_database.count(i))
+ return m_database[i];
else
return "";
}
-bool Database_Dummy::deleteBlock(v3s16 blockpos)
+bool Database_Dummy::deleteBlock(const v3s16 &pos)
{
- m_database.erase(getBlockAsInteger(blockpos));
+ m_database.erase(getBlockAsInteger(pos));
return true;
}
void Database_Dummy::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
- for(std::map<u64, std::string>::iterator x = m_database.begin(); x != m_database.end(); ++x)
- {
- v3s16 p = getIntegerAsBlock(x->first);
- //dstream<<"block_i="<<block_i<<" p="<<PP(p)<<std::endl;
- dst.push_back(p);
+ for (std::map<s64, std::string>::const_iterator x = m_database.begin();
+ x != m_database.end(); ++x) {
+ dst.push_back(getIntegerAsBlock(x->first));
}
}
-Database_Dummy::~Database_Dummy()
-{
- m_database.clear();
-}
#include "database.h"
#include "irrlichttypes.h"
-class ServerMap;
-
class Database_Dummy : public Database
{
public:
- Database_Dummy(ServerMap *map);
- virtual void beginSave();
- virtual void endSave();
- virtual bool saveBlock(v3s16 blockpos, std::string &data);
- virtual std::string loadBlock(v3s16 blockpos);
- virtual bool deleteBlock(v3s16 blockpos);
+ virtual bool saveBlock(const v3s16 &pos, const std::string &data);
+ virtual std::string loadBlock(const v3s16 &pos);
+ virtual bool deleteBlock(const v3s16 &pos);
virtual void listAllLoadableBlocks(std::vector<v3s16> &dst);
- virtual int Initialized(void);
- ~Database_Dummy();
+
private:
- ServerMap *srvmap;
- std::map<u64, std::string> m_database;
+ std::map<s64, std::string> m_database;
};
+
#endif
+
#if USE_LEVELDB
#include "database-leveldb.h"
-#include "leveldb/db.h"
-#include "map.h"
-#include "mapsector.h"
-#include "mapblock.h"
-#include "serialization.h"
-#include "main.h"
-#include "settings.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 FileNotGoodException(std::string("LevelDB error: ") + (s).ToString()); \
+ throw FileNotGoodException(std::string("LevelDB error: ") + \
+ (s).ToString()); \
}
-Database_LevelDB::Database_LevelDB(ServerMap *map, std::string savedir)
+
+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);
+ leveldb::Status status = leveldb::DB::Open(options,
+ savedir + DIR_DELIM + "map.db", &m_database);
ENSURE_STATUS_OK(status);
- srvmap = map;
}
-int Database_LevelDB::Initialized(void)
+Database_LevelDB::~Database_LevelDB()
{
- return 1;
+ delete m_database;
}
-void Database_LevelDB::beginSave() {}
-void Database_LevelDB::endSave() {}
-
-bool Database_LevelDB::saveBlock(v3s16 blockpos, std::string &data)
+bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data)
{
leveldb::Status status = m_database->Put(leveldb::WriteOptions(),
- i64tos(getBlockAsInteger(blockpos)), data);
+ i64tos(getBlockAsInteger(pos)), data);
if (!status.ok()) {
errorstream << "WARNING: saveBlock: LevelDB error saving block "
- << PP(blockpos) << ": " << status.ToString() << std::endl;
+ << PP(pos) << ": " << status.ToString() << std::endl;
return false;
}
return true;
}
-std::string Database_LevelDB::loadBlock(v3s16 blockpos)
+std::string Database_LevelDB::loadBlock(const v3s16 &pos)
{
std::string datastr;
leveldb::Status status = m_database->Get(leveldb::ReadOptions(),
- i64tos(getBlockAsInteger(blockpos)), &datastr);
+ i64tos(getBlockAsInteger(pos)), &datastr);
if(status.ok())
return datastr;
return "";
}
-bool Database_LevelDB::deleteBlock(v3s16 blockpos)
+bool Database_LevelDB::deleteBlock(const v3s16 &pos)
{
leveldb::Status status = m_database->Delete(leveldb::WriteOptions(),
- i64tos(getBlockAsInteger(blockpos)));
+ i64tos(getBlockAsInteger(pos)));
if (!status.ok()) {
errorstream << "WARNING: deleteBlock: LevelDB error deleting block "
- << PP(blockpos) << ": " << status.ToString() << std::endl;
+ << PP(pos) << ": " << status.ToString() << std::endl;
return false;
}
delete it;
}
-Database_LevelDB::~Database_LevelDB()
-{
- delete m_database;
-}
-#endif
+#endif // USE_LEVELDB
+
#include "leveldb/db.h"
#include <string>
-class ServerMap;
-
class Database_LevelDB : public Database
{
public:
- Database_LevelDB(ServerMap *map, std::string savedir);
- virtual void beginSave();
- virtual void endSave();
- virtual bool saveBlock(v3s16 blockpos, std::string &data);
- virtual std::string loadBlock(v3s16 blockpos);
- virtual bool deleteBlock(v3s16 blockpos);
- virtual void listAllLoadableBlocks(std::vector<v3s16> &dst);
- virtual int Initialized(void);
+ Database_LevelDB(const std::string &savedir);
~Database_LevelDB();
+
+ virtual bool saveBlock(const v3s16 &pos, const std::string &data);
+ virtual std::string loadBlock(const v3s16 &pos);
+ virtual bool deleteBlock(const v3s16 &pos);
+ virtual void listAllLoadableBlocks(std::vector<v3s16> &dst);
+
private:
- ServerMap *srvmap;
- leveldb::DB* m_database;
+ leveldb::DB *m_database;
};
+
+#endif // USE_LEVELDB
+
#endif
-#endif
+
#include "config.h"
#if USE_REDIS
-/*
- Redis databases
-*/
-
#include "database-redis.h"
-#include <hiredis.h>
-#include "map.h"
-#include "mapsector.h"
-#include "mapblock.h"
-#include "serialization.h"
-#include "main.h"
#include "settings.h"
#include "log.h"
-#include "filesys.h"
+#include "exceptions.h"
+#include "util/string.h"
+
+#include <hiredis.h>
+#include <cassert>
-Database_Redis::Database_Redis(ServerMap *map, std::string savedir)
+Database_Redis::Database_Redis(Settings &conf)
{
- Settings conf;
- conf.readConfigFile((std::string(savedir) + DIR_DELIM + "world.mt").c_str());
std::string tmp;
try {
- tmp = conf.get("redis_address");
- hash = conf.get("redis_hash");
- } catch(SettingNotFoundException e) {
- throw SettingNotFoundException("Set redis_address and redis_hash in world.mt to use the redis backend");
+ 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;
ctx = redisConnect(addr, port);
- if(!ctx)
+ if (!ctx) {
throw FileNotGoodException("Cannot allocate redis context");
- else if(ctx->err) {
+ } else if(ctx->err) {
std::string err = std::string("Connection error: ") + ctx->errstr;
redisFree(ctx);
throw FileNotGoodException(err);
}
- srvmap = map;
}
-int Database_Redis::Initialized(void)
+Database_Redis::~Database_Redis()
{
- return 1;
+ redisFree(ctx);
}
void Database_Redis::beginSave() {
- redisReply *reply;
- reply = (redisReply*) redisCommand(ctx, "MULTI");
- if(!reply)
- throw FileNotGoodException(std::string("redis command 'MULTI' failed: ") + ctx->errstr);
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "MULTI"));
+ if (!reply) {
+ throw FileNotGoodException(std::string(
+ "Redis command 'MULTI' failed: ") + ctx->errstr);
+ }
freeReplyObject(reply);
}
void Database_Redis::endSave() {
- redisReply *reply;
- reply = (redisReply*) redisCommand(ctx, "EXEC");
- if(!reply)
- throw FileNotGoodException(std::string("redis command 'EXEC' failed: ") + ctx->errstr);
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "EXEC"));
+ if (!reply) {
+ throw FileNotGoodException(std::string(
+ "Redis command 'EXEC' failed: ") + ctx->errstr);
+ }
freeReplyObject(reply);
}
-bool Database_Redis::saveBlock(v3s16 blockpos, std::string &data)
+bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data)
{
- std::string tmp = i64tos(getBlockAsInteger(blockpos));
+ std::string tmp = i64tos(getBlockAsInteger(pos));
- redisReply *reply = (redisReply *)redisCommand(ctx, "HSET %s %s %b",
- hash.c_str(), tmp.c_str(), data.c_str(), data.size());
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HSET %s %s %b",
+ hash.c_str(), tmp.c_str(), data.c_str(), data.size()));
if (!reply) {
errorstream << "WARNING: saveBlock: redis command 'HSET' failed on "
- "block " << PP(blockpos) << ": " << ctx->errstr << std::endl;
+ "block " << PP(pos) << ": " << ctx->errstr << std::endl;
freeReplyObject(reply);
return false;
}
if (reply->type == REDIS_REPLY_ERROR) {
- errorstream << "WARNING: saveBlock: saving block " << PP(blockpos)
+ errorstream << "WARNING: saveBlock: saving block " << PP(pos)
<< "failed" << std::endl;
freeReplyObject(reply);
return false;
return true;
}
-std::string Database_Redis::loadBlock(v3s16 blockpos)
+std::string Database_Redis::loadBlock(const v3s16 &pos)
{
- std::string tmp = i64tos(getBlockAsInteger(blockpos));
- redisReply *reply;
- reply = (redisReply*) redisCommand(ctx, "HGET %s %s", hash.c_str(), tmp.c_str());
+ 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 FileNotGoodException(std::string("redis command 'HGET %s %s' failed: ") + ctx->errstr);
- if(reply->type != REDIS_REPLY_STRING)
+ if (!reply) {
+ throw FileNotGoodException(std::string(
+ "Redis command 'HGET %s %s' failed: ") + ctx->errstr);
+ } else if (reply->type != REDIS_REPLY_STRING) {
return "";
+ }
std::string str(reply->str, reply->len);
freeReplyObject(reply); // std::string copies the memory so this won't cause any problems
return str;
}
-bool Database_Redis::deleteBlock(v3s16 blockpos)
+bool Database_Redis::deleteBlock(const v3s16 &pos)
{
- std::string tmp = i64tos(getBlockAsInteger(blockpos));
+ std::string tmp = i64tos(getBlockAsInteger(pos));
- redisReply *reply = (redisReply *)redisCommand(ctx, "HDEL %s %s",
- hash.c_str(), tmp.c_str());
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
+ "HDEL %s %s", hash.c_str(), tmp.c_str()));
if (!reply) {
- errorstream << "WARNING: deleteBlock: redis command 'HDEL' failed on "
- "block " << PP(blockpos) << ": " << ctx->errstr << std::endl;
- freeReplyObject(reply);
- return false;
- }
-
- if (reply->type == REDIS_REPLY_ERROR) {
- errorstream << "WARNING: deleteBlock: deleting block " << PP(blockpos)
- << "failed" << std::endl;
+ throw FileNotGoodException(std::string(
+ "Redis command 'HDEL %s %s' failed: ") + ctx->errstr);
+ } else if (reply->type == REDIS_REPLY_ERROR) {
+ errorstream << "WARNING: deleteBlock: deleting block " << PP(pos)
+ << " failed" << std::endl;
freeReplyObject(reply);
return false;
}
void Database_Redis::listAllLoadableBlocks(std::vector<v3s16> &dst)
{
- redisReply *reply;
- reply = (redisReply*) redisCommand(ctx, "HKEYS %s", hash.c_str());
- if(!reply)
- throw FileNotGoodException(std::string("redis command 'HKEYS %s' failed: ") + ctx->errstr);
- if(reply->type != REDIS_REPLY_ARRAY)
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HKEYS %s", hash.c_str()));
+ if (!reply) {
+ throw FileNotGoodException(std::string(
+ "Redis command 'HKEYS %s' failed: ") + ctx->errstr);
+ } else if (reply->type != REDIS_REPLY_ARRAY) {
throw FileNotGoodException("Failed to get keys from database");
- for(size_t i = 0; i < reply->elements; i++) {
+ }
+ 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)));
}
freeReplyObject(reply);
}
-Database_Redis::~Database_Redis()
-{
- redisFree(ctx);
-}
-#endif
+#endif // USE_REDIS
+
#include <hiredis.h>
#include <string>
-class ServerMap;
+class Settings;
class Database_Redis : public Database
{
public:
- Database_Redis(ServerMap *map, std::string savedir);
+ Database_Redis(Settings &conf);
+ ~Database_Redis();
+
virtual void beginSave();
virtual void endSave();
- virtual bool saveBlock(v3s16 blockpos, std::string &data);
- virtual std::string loadBlock(v3s16 blockpos);
- virtual bool deleteBlock(v3s16 blockpos);
+
+ virtual bool saveBlock(const v3s16 &pos, const std::string &data);
+ virtual std::string loadBlock(const v3s16 &pos);
+ virtual bool deleteBlock(const v3s16 &pos);
virtual void listAllLoadableBlocks(std::vector<v3s16> &dst);
- virtual int Initialized(void);
- ~Database_Redis();
+
private:
- ServerMap *srvmap;
redisContext *ctx;
std::string hash;
};
+
+#endif // USE_REDIS
+
#endif
-#endif
+
*/
/*
- SQLite format specification:
- - Initially only replaces sectors/ and sectors2/
-
- If map.sqlite does not exist in the save dir
- or the block was not found in the database
- the map will try to load from sectors folder.
- In either case, map.sqlite will be created
- and all future saves will save there.
-
- Structure of map.sqlite:
- Tables:
- blocks
- (PK) INT pos
- BLOB data
+SQLite format specification:
+ blocks:
+ (PK) INT id
+ BLOB data
*/
#include "database-sqlite3.h"
-#include "map.h"
-#include "mapsector.h"
-#include "mapblock.h"
-#include "serialization.h"
-#include "main.h"
-#include "settings.h"
#include "log.h"
#include "filesys.h"
+#include "exceptions.h"
+#include "main.h"
+#include "settings.h"
+#include "util/string.h"
+
+#include <cassert>
+
+
+#define SQLRES(s, r) \
+ if ((s) != (r)) { \
+ throw FileNotGoodException(std::string(\
+ "SQLite3 database error (" \
+ __FILE__ ":" TOSTRING(__LINE__) \
+ "): ") +\
+ sqlite3_errmsg(m_database)); \
+ }
+#define SQLOK(s) SQLRES(s, SQLITE_OK)
+
+#define PREPARE_STATEMENT(name, query) \
+ SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL))
+
+#define FINALIZE_STATEMENT(statement) \
+ if (sqlite3_finalize(statement) != SQLITE_OK) { \
+ throw FileNotGoodException(std::string( \
+ "SQLite3: Failed to finalize " #statement ": ") + \
+ sqlite3_errmsg(m_database)); \
+ }
-Database_SQLite3::Database_SQLite3(ServerMap *map, std::string savedir)
-{
- m_database = NULL;
- m_database_read = NULL;
- m_database_write = NULL;
- m_database_list = NULL;
- m_database_delete = NULL;
- m_savedir = savedir;
- srvmap = map;
-}
-int Database_SQLite3::Initialized(void)
+Database_SQLite3::Database_SQLite3(const std::string &savedir) :
+ 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)
{
- return m_database ? 1 : 0;
}
void Database_SQLite3::beginSave() {
verifyDatabase();
- if(sqlite3_exec(m_database, "BEGIN;", NULL, NULL, NULL) != SQLITE_OK)
- errorstream<<"WARNING: beginSave() failed, saving might be slow.";
+ SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE);
+ sqlite3_reset(m_stmt_begin);
}
void Database_SQLite3::endSave() {
verifyDatabase();
- if(sqlite3_exec(m_database, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK)
- errorstream<<"WARNING: endSave() failed, map might not have saved.";
+ SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE);
+ sqlite3_reset(m_stmt_end);
}
-void Database_SQLite3::createDirs(std::string path)
+void Database_SQLite3::openDatabase()
{
- if(fs::CreateAllDirs(path) == false)
- {
- infostream<<DTIME<<"Database_SQLite3: Failed to create directory "
- <<"\""<<path<<"\""<<std::endl;
- throw BaseException("Database_SQLite3 failed to create directory");
- }
-}
+ if (m_database) return;
-void Database_SQLite3::verifyDatabase() {
- if(m_database)
- return;
-
- std::string dbp = m_savedir + DIR_DELIM "map.sqlite";
- bool needs_create = false;
- int d;
+ std::string dbp = m_savedir + DIR_DELIM + "map.sqlite";
// Open the database connection
- createDirs(m_savedir); // ?
+ if (!fs::CreateAllDirs(m_savedir)) {
+ infostream << "Database_SQLite3: Failed to create directory \""
+ << m_savedir << "\"" << std::endl;
+ throw FileNotGoodException("Failed to create database "
+ "save directory");
+ }
- if(!fs::PathExists(dbp))
- needs_create = true;
+ bool needs_create = !fs::PathExists(dbp);
- d = sqlite3_open_v2(dbp.c_str(), &m_database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
- if(d != SQLITE_OK) {
- errorstream<<"SQLite3 database failed to open: "<<sqlite3_errmsg(m_database)<<std::endl;
+ if (sqlite3_open_v2(dbp.c_str(), &m_database,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+ NULL) != SQLITE_OK) {
+ errorstream << "SQLite3 database failed to open: "
+ << sqlite3_errmsg(m_database) << std::endl;
throw FileNotGoodException("Cannot open database file");
}
- if(needs_create)
+ if (needs_create) {
createDatabase();
+ }
- std::string querystr = std::string("PRAGMA synchronous = ")
+ std::string query_str = std::string("PRAGMA synchronous = ")
+ itos(g_settings->getU16("sqlite_synchronous"));
- d = sqlite3_exec(m_database, querystr.c_str(), NULL, NULL, NULL);
- if(d != SQLITE_OK) {
- errorstream<<"Database pragma set failed: "
- <<sqlite3_errmsg(m_database)<<std::endl;
- throw FileNotGoodException("Cannot set pragma");
- }
+ SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL));
+}
- d = sqlite3_prepare(m_database, "SELECT `data` FROM `blocks` WHERE `pos`=? LIMIT 1", -1, &m_database_read, NULL);
- if(d != SQLITE_OK) {
- errorstream<<"SQLite3 read statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
- throw FileNotGoodException("Cannot prepare read statement");
- }
+void Database_SQLite3::verifyDatabase()
+{
+ if (m_initialized) return;
+
+ openDatabase();
+
+ PREPARE_STATEMENT(begin, "BEGIN");
+ PREPARE_STATEMENT(end, "COMMIT");
+ PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1");
#ifdef __ANDROID__
- d = sqlite3_prepare(m_database, "INSERT INTO `blocks` VALUES(?, ?);", -1, &m_database_write, NULL);
+ PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
#else
- d = sqlite3_prepare(m_database, "REPLACE INTO `blocks` VALUES(?, ?);", -1, &m_database_write, NULL);
+ PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
#endif
- if(d != SQLITE_OK) {
- errorstream<<"SQLite3 write statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
- throw FileNotGoodException("Cannot prepare write statement");
- }
+ PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?");
+ PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`");
- d = sqlite3_prepare(m_database, "DELETE FROM `blocks` WHERE `pos`=?;", -1, &m_database_delete, NULL);
- if(d != SQLITE_OK) {
- infostream<<"WARNING: SQLite3 database delete statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
- throw FileNotGoodException("Cannot prepare delete statement");
- }
+ m_initialized = true;
- d = sqlite3_prepare(m_database, "SELECT `pos` FROM `blocks`", -1, &m_database_list, NULL);
- if(d != SQLITE_OK) {
- infostream<<"SQLite3 list statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl;
- throw FileNotGoodException("Cannot prepare read statement");
- }
+ verbosestream << "ServerMap: SQLite3 database opened." << std::endl;
+}
- infostream<<"ServerMap: SQLite3 database opened"<<std::endl;
+inline void Database_SQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index)
+{
+ SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)));
}
-bool Database_SQLite3::deleteBlock(v3s16 blockpos)
+bool Database_SQLite3::deleteBlock(const v3s16 &pos)
{
verifyDatabase();
- if (sqlite3_bind_int64(m_database_delete, 1,
- getBlockAsInteger(blockpos)) != SQLITE_OK) {
- errorstream << "WARNING: Could not bind block position for delete: "
- << sqlite3_errmsg(m_database) << std::endl;
- }
+ bindPos(m_stmt_delete, pos);
+
+ bool good = sqlite3_step(m_stmt_delete) == SQLITE_DONE;
+ sqlite3_reset(m_stmt_delete);
- if (sqlite3_step(m_database_delete) != SQLITE_DONE) {
+ if (!good) {
errorstream << "WARNING: deleteBlock: Block failed to delete "
- << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
- sqlite3_reset(m_database_delete);
- return false;
+ << PP(pos) << ": " << sqlite3_errmsg(m_database) << std::endl;
}
-
- sqlite3_reset(m_database_delete);
- return true;
+ return good;
}
-bool Database_SQLite3::saveBlock(v3s16 blockpos, std::string &data)
+bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data)
{
verifyDatabase();
- s64 bkey = getBlockAsInteger(blockpos);
-
#ifdef __ANDROID__
/**
- * Note: For some unknown reason sqlite3 fails to REPLACE blocks on android,
- * deleting them and inserting first works.
+ * Note: For some unknown reason SQLite3 fails to REPLACE blocks on Android,
+ * deleting them and then inserting works.
*/
- if (sqlite3_bind_int64(m_database_read, 1, bkey) != SQLITE_OK) {
- infostream << "WARNING: Could not bind block position for load: "
- << sqlite3_errmsg(m_database)<<std::endl;
- }
+ bindPos(m_stmt_read, pos);
- int step_result = sqlite3_step(m_database_read);
- sqlite3_reset(m_database_read);
-
- if (step_result == SQLITE_ROW) {
- if (sqlite3_bind_int64(m_database_delete, 1, bkey) != SQLITE_OK) {
- infostream << "WARNING: Could not bind block position for delete: "
- << sqlite3_errmsg(m_database)<<std::endl;
- }
-
- if (sqlite3_step(m_database_delete) != SQLITE_DONE) {
- errorstream << "WARNING: saveBlock: Block failed to delete "
- << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
- return false;
- }
- sqlite3_reset(m_database_delete);
+ if (sqlite3_step(m_stmt_read) == SQLITE_ROW) {
+ deleteBlock(pos);
}
+ sqlite3_reset(m_stmt_read);
#endif
- if (sqlite3_bind_int64(m_database_write, 1, bkey) != SQLITE_OK) {
- errorstream << "WARNING: saveBlock: Block position failed to bind: "
- << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
- sqlite3_reset(m_database_write);
- return false;
- }
-
- if (sqlite3_bind_blob(m_database_write, 2, (void *)data.c_str(),
- data.size(), NULL) != SQLITE_OK) {
- errorstream << "WARNING: saveBlock: Block data failed to bind: "
- << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
- sqlite3_reset(m_database_write);
- return false;
- }
-
- if (sqlite3_step(m_database_write) != SQLITE_DONE) {
- errorstream << "WARNING: saveBlock: Block failed to save "
- << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl;
- sqlite3_reset(m_database_write);
- return false;
- }
+ bindPos(m_stmt_write, pos);
+ SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL));
- sqlite3_reset(m_database_write);
+ SQLRES(sqlite3_step(m_stmt_write), SQLITE_DONE)
+ sqlite3_reset(m_stmt_write);
return true;
}
-std::string Database_SQLite3::loadBlock(v3s16 blockpos)
+std::string Database_SQLite3::loadBlock(const v3s16 &pos)
{
verifyDatabase();
- if (sqlite3_bind_int64(m_database_read, 1, getBlockAsInteger(blockpos)) != SQLITE_OK) {
- errorstream << "Could not bind block position for load: "
- << sqlite3_errmsg(m_database)<<std::endl;
- }
+ bindPos(m_stmt_read, pos);
- if (sqlite3_step(m_database_read) == SQLITE_ROW) {
- const char *data = (const char *) sqlite3_column_blob(m_database_read, 0);
- size_t len = sqlite3_column_bytes(m_database_read, 0);
-
- std::string s = "";
- if(data)
- s = std::string(data, len);
+ 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);
- sqlite3_step(m_database_read);
- // We should never get more than 1 row, so ok to reset
- sqlite3_reset(m_database_read);
+ std::string s;
+ if (data)
+ s = std::string(data, len);
- return s;
- }
+ sqlite3_step(m_stmt_read);
+ // We should never get more than 1 row, so ok to reset
+ sqlite3_reset(m_stmt_read);
- sqlite3_reset(m_database_read);
- return "";
+ return s;
}
void Database_SQLite3::createDatabase()
{
- int e;
assert(m_database);
- e = sqlite3_exec(m_database,
- "CREATE TABLE IF NOT EXISTS `blocks` ("
- "`pos` INT NOT NULL PRIMARY KEY,"
- "`data` BLOB"
- ");"
- , NULL, NULL, NULL);
- if(e != SQLITE_OK)
- throw FileNotGoodException("Could not create sqlite3 database structure");
- else
- infostream<<"ServerMap: SQLite3 database structure was created";
+ SQLOK(sqlite3_exec(m_database,
+ "CREATE TABLE IF NOT EXISTS `blocks` (\n"
+ " `pos` INT PRIMARY KEY,\n"
+ " `data` BLOB\n"
+ ");\n",
+ NULL, NULL, NULL));
}
{
verifyDatabase();
- while(sqlite3_step(m_database_list) == SQLITE_ROW) {
- sqlite3_int64 block_i = sqlite3_column_int64(m_database_list, 0);
- v3s16 p = getIntegerAsBlock(block_i);
- //dstream<<"block_i="<<block_i<<" p="<<PP(p)<<std::endl;
- dst.push_back(p);
+ while (sqlite3_step(m_stmt_list) == SQLITE_ROW) {
+ dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0)));
}
+ sqlite3_reset(m_stmt_list);
}
-
-#define FINALIZE_STATEMENT(statement) \
- if ( statement ) \
- rc = sqlite3_finalize(statement); \
- if ( rc != SQLITE_OK ) \
- errorstream << "Database_SQLite3::~Database_SQLite3():" \
- << "Failed to finalize: " << #statement << ": rc=" << rc << std::endl;
-
Database_SQLite3::~Database_SQLite3()
{
- int rc = SQLITE_OK;
-
- FINALIZE_STATEMENT(m_database_read)
- FINALIZE_STATEMENT(m_database_write)
- FINALIZE_STATEMENT(m_database_list)
- FINALIZE_STATEMENT(m_database_delete)
-
- if(m_database)
- rc = sqlite3_close(m_database);
-
- if (rc != SQLITE_OK) {
+ 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)
+
+ if (sqlite3_close(m_database) != SQLITE_OK) {
errorstream << "Database_SQLite3::~Database_SQLite3(): "
- << "Failed to close database: rc=" << rc << std::endl;
+ << "Failed to close database: "
+ << sqlite3_errmsg(m_database) << std::endl;
}
}
+
#include "sqlite3.h"
}
-class ServerMap;
-
class Database_SQLite3 : public Database
{
public:
- Database_SQLite3(ServerMap *map, std::string savedir);
+ Database_SQLite3(const std::string &savedir);
+
virtual void beginSave();
virtual void endSave();
- virtual bool saveBlock(v3s16 blockpos, std::string &data);
- virtual std::string loadBlock(v3s16 blockpos);
- virtual bool deleteBlock(v3s16 blockpos);
+ virtual bool saveBlock(const v3s16 &pos, const std::string &data);
+ virtual std::string loadBlock(const v3s16 &pos);
+ virtual bool deleteBlock(const v3s16 &pos);
virtual void listAllLoadableBlocks(std::vector<v3s16> &dst);
- virtual int Initialized(void);
+ virtual bool initialized() const { return m_initialized; }
~Database_SQLite3();
-private:
- ServerMap *srvmap;
- std::string m_savedir;
- sqlite3 *m_database;
- sqlite3_stmt *m_database_read;
- sqlite3_stmt *m_database_write;
- sqlite3_stmt *m_database_delete;
- sqlite3_stmt *m_database_list;
+private:
+ // Open the database
+ void openDatabase();
// Create the database structure
void createDatabase();
- // Verify we can read/write to the database
+ // Open and initialize the database if needed
void verifyDatabase();
- void createDirs(std::string path);
+
+ void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index=1);
+
+ bool m_initialized;
+
+ std::string m_savedir;
+
+ 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;
};
#endif
+
}
-s64 Database::getBlockAsInteger(const v3s16 pos) const
+s64 Database::getBlockAsInteger(const v3s16 &pos)
{
return (u64) pos.Z * 0x1000000 +
(u64) pos.Y * 0x1000 +
}
-v3s16 Database::getIntegerAsBlock(s64 i) const
+v3s16 Database::getIntegerAsBlock(s64 i)
{
v3s16 pos;
pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
class Database
{
public:
- virtual void beginSave() = 0;
- virtual void endSave() = 0;
-
- virtual bool saveBlock(v3s16 blockpos, std::string &data) = 0;
- virtual std::string loadBlock(v3s16 blockpos) = 0;
- virtual bool deleteBlock(v3s16 blockpos) = 0;
- s64 getBlockAsInteger(const v3s16 pos) const;
- v3s16 getIntegerAsBlock(s64 i) const;
+ virtual ~Database() {}
+
+ virtual void beginSave() {}
+ virtual void endSave() {}
+
+ virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0;
+ virtual std::string loadBlock(const v3s16 &pos) = 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;
- virtual int Initialized(void)=0;
- virtual ~Database() {};
+
+ virtual bool initialized() const { return true; }
};
+
#endif
+
static bool migrate_database(const GameParams &game_params, const Settings &cmd_args,
Server *server)
{
+ std::string migrate_to = cmd_args.get("migrate");
Settings world_mt;
- bool success = world_mt.readConfigFile((game_params.world_path
- + DIR_DELIM + "world.mt").c_str());
+ std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
+ bool success = world_mt.readConfigFile(world_mt_path.c_str());
if (!success) {
errorstream << "Cannot read world.mt" << std::endl;
- return false;
+ return 1;
}
-
if (!world_mt.exists("backend")) {
- errorstream << "Please specify your current backend in world.mt file:"
- << std::endl << " backend = {sqlite3|leveldb|redis|dummy}"
- << std::endl;
- return false;
+ errorstream << "Please specify your current backend in world.mt:"
+ << std::endl;
+ errorstream << " backend = {sqlite3|leveldb|redis|dummy}"
+ << std::endl;
+ return 1;
}
-
std::string backend = world_mt.get("backend");
Database *new_db;
- std::string migrate_to = cmd_args.get("migrate");
-
if (backend == migrate_to) {
- errorstream << "Cannot migrate: new backend is same as the old one"
- << std::endl;
- return false;
+ errorstream << "Cannot migrate: new backend is same"
+ <<" as the old one" << std::endl;
+ return 1;
}
-
if (migrate_to == "sqlite3")
- new_db = new Database_SQLite3(&(ServerMap&)server->getMap(),
- game_params.world_path);
-#if USE_LEVELDB
+ new_db = new Database_SQLite3(game_params.world_path);
+ #if USE_LEVELDB
else if (migrate_to == "leveldb")
- new_db = new Database_LevelDB(&(ServerMap&)server->getMap(),
- game_params.world_path);
-#endif
-#if USE_REDIS
+ new_db = new Database_LevelDB(game_params.world_path);
+ #endif
+ #if USE_REDIS
else if (migrate_to == "redis")
- new_db = new Database_Redis(&(ServerMap&)server->getMap(),
- game_params.world_path);
-#endif
+ new_db = new Database_Redis(world_mt);
+ #endif
else {
- errorstream << "Migration to " << migrate_to << " is not supported"
- << std::endl;
- return false;
+ errorstream << "Migration to " << migrate_to
+ << " is not supported" << std::endl;
+ return 1;
}
std::vector<v3s16> blocks;
- ServerMap &old_map = ((ServerMap&)server->getMap());
+ ServerMap &old_map = (ServerMap &) server->getMap();
old_map.listAllLoadableBlocks(blocks);
int count = 0;
new_db->beginSave();
}
++count;
if (count % 500 == 0)
- actionstream << "Migrated " << count << " blocks "
- << (100.0 * count / blocks.size()) << "% completed" << std::endl;
+ actionstream << "Migrated " << count << " blocks "
+ << (100.0 * count / blocks.size()) << "% completed" << std::endl;
}
new_db->endSave();
delete new_db;
actionstream << "Successfully migrated " << count << " blocks" << std::endl;
world_mt.set("backend", migrate_to);
- if (!world_mt.updateConfigFile(
- (game_params.world_path+ DIR_DELIM + "world.mt").c_str()))
+ if (!world_mt.updateConfigFile(world_mt_path.c_str()))
errorstream << "Failed to update world.mt!" << std::endl;
else
actionstream << "world.mt updated" << std::endl;
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
-/*
- SQLite format specification:
- - Initially only replaces sectors/ and sectors2/
-
- If map.sqlite does not exist in the save dir
- or the block was not found in the database
- the map will try to load from sectors folder.
- In either case, map.sqlite will be created
- and all future saves will save there.
-
- Structure of map.sqlite:
- Tables:
- blocks
- (PK) INT pos
- BLOB data
-*/
/*
Map
bool succeeded = conf.readConfigFile(conf_path.c_str());
if (!succeeded || !conf.exists("backend")) {
// fall back to sqlite3
- dbase = new Database_SQLite3(this, savedir);
conf.set("backend", "sqlite3");
- } else {
- std::string backend = conf.get("backend");
- if (backend == "dummy")
- dbase = new Database_Dummy(this);
- else if (backend == "sqlite3")
- dbase = new Database_SQLite3(this, savedir);
- #if USE_LEVELDB
- else if (backend == "leveldb")
- dbase = new Database_LevelDB(this, savedir);
- #endif
- #if USE_REDIS
- else if (backend == "redis")
- dbase = new Database_Redis(this, savedir);
- #endif
- else
- throw BaseException("Unknown map backend");
}
+ std::string backend = conf.get("backend");
+ if (backend == "dummy")
+ dbase = new Database_Dummy();
+ else if (backend == "sqlite3")
+ dbase = new Database_SQLite3(savedir);
+ #if USE_LEVELDB
+ else if (backend == "leveldb")
+ dbase = new Database_LevelDB(savedir);
+ #endif
+ #if USE_REDIS
+ else if (backend == "redis")
+ dbase = new Database_Redis(conf);
+ #endif
+ else
+ throw BaseException("Unknown map backend");
+
+ if (!conf.updateConfigFile(conf_path.c_str()))
+ errorstream << "ServerMap::ServerMap(): Failed to update world.mt!" << std::endl;
m_savedir = savedir;
m_map_saving_enabled = false;
}
bool ServerMap::loadFromFolders() {
- if(!dbase->Initialized() && !fs::PathExists(m_savedir + DIR_DELIM + "map.sqlite")) // ?
+ if(!dbase->initialized() &&
+ !fs::PathExists(m_savedir + DIR_DELIM + "map.sqlite"))
return true;
return false;
}
{
case 1:
snprintf(cc, 9, "%.4x%.4x",
- (unsigned int)pos.X&0xffff,
- (unsigned int)pos.Y&0xffff);
+ (unsigned int) pos.X & 0xffff,
+ (unsigned int) pos.Y & 0xffff);
return m_savedir + DIR_DELIM + "sectors" + DIR_DELIM + cc;
case 2:
- snprintf(cc, 9, "%.3x" DIR_DELIM "%.3x",
- (unsigned int)pos.X&0xfff,
- (unsigned int)pos.Y&0xfff);
+ snprintf(cc, 9, (std::string("%.3x") + DIR_DELIM + "%.3x").c_str(),
+ (unsigned int) pos.X & 0xfff,
+ (unsigned int) pos.Y & 0xfff);
return m_savedir + DIR_DELIM + "sectors2" + DIR_DELIM + cc;
default:
{
// New layout
fs::RemoveLastPathComponent(dirname, &component, 2);
- r = sscanf(component.c_str(), "%3x" DIR_DELIM "%3x", &x, &y);
+ r = sscanf(component.c_str(), (std::string("%3x") + DIR_DELIM + "%3x").c_str(), &x, &y);
// Sign-extend the 12 bit values up to 16 bits...
- if(x&0x800) x|=0xF000;
- if(y&0x800) y|=0xF000;
+ if(x & 0x800) x |= 0xF000;
+ if(y & 0x800) y |= 0xF000;
}
else
{
std::string data = o.str();
bool ret = db->saveBlock(p3d, data);
- if(ret) {
+ if (ret) {
// We just wrote it to the disk so clear modified flag
block->resetModified();
}
{
DSTACK(__FUNCTION_NAME);
- std::string fullpath = sectordir+DIR_DELIM+blockfile;
+ std::string fullpath = sectordir + DIR_DELIM + blockfile;
try {
std::ifstream is(fullpath.c_str(), std::ios_base::binary);
*/
std::string blockfilename = getBlockFilename(blockpos);
- if(fs::PathExists(sectordir+DIR_DELIM+blockfilename) == false)
+ if(fs::PathExists(sectordir + DIR_DELIM + blockfilename) == false)
return NULL;
/*
// Returns true if sector now resides in memory
//bool deFlushSector(v2s16 p2d);
- bool saveBlock(MapBlock *block, Database *db);
bool saveBlock(MapBlock *block);
+ static bool saveBlock(MapBlock *block, Database *db);
// This will generate a sector with getSector if not found.
void loadBlock(std::string sectordir, std::string blockfile, MapSector *sector, bool save_after_load=false);
MapBlock* loadBlock(v3s16 p);
sector->insertBlock(block);
}
- if (localdb != NULL) {
- ((ServerMap&) localserver->getMap()).saveBlock(block, localdb);
+ if (m_localdb) {
+ ServerMap::saveBlock(block, m_localdb);
}
/*