X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Frollback.cpp;h=7267a50f6e4234ba425948e7817d8bedc560f41b;hb=ba15c98e4d5d7f4bc515e351d6af1a084d46092e;hp=db3f01702b980c8abb356dcd1d107ef320bfd537;hpb=a9d8df83d228635594b75a563a0a8d906b3b883a;p=oweals%2Fminetest.git diff --git a/src/rollback.cpp b/src/rollback.cpp index db3f01702..7267a50f6 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -1,6 +1,6 @@ /* -Minetest-c55 -Copyright (C) 2012 celeron55, Perttu Ahola +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola 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 @@ -27,333 +27,924 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "util/serialize.h" #include "util/string.h" -#include "strfnd.h" #include "util/numeric.h" #include "inventorymanager.h" // deserializing InventoryLocations +#include "sqlite3.h" +#include "filesys.h" -#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" +#define POINTS_PER_NODE (16.0) -// Get nearness factor for subject's action for this action -// Return value: 0 = impossible, >0 = factor -static float getSuspectNearness(bool is_guess, v3s16 suspect_p, int suspect_t, - v3s16 action_p, int action_t) +#define SQLRES(f, good) \ + if ((f) != (good)) {\ + throw FileNotGoodException(std::string("RollbackManager: " \ + "SQLite3 error (" __FILE__ ":" TOSTRING(__LINE__) \ + "): ") + sqlite3_errmsg(db)); \ + } +#define SQLOK(f) SQLRES(f, SQLITE_OK) + + +class ItemStackRow : public ItemStack { +public: + ItemStackRow & operator = (const ItemStack & other) + { + *static_cast(this) = other; + return *this; + } + + int id; +}; + +struct ActionRow { + int id; + int actor; + time_t timestamp; + int type; + std::string location, list; + int index, add; + ItemStackRow stack; + int nodeMeta; + int x, y, z; + int oldNode; + int oldParam1, oldParam2; + std::string oldMeta; + int newNode; + int newParam1, newParam2; + std::string newMeta; + int guessed; +}; + + +struct Entity { + int id; + std::string name; +}; + + + +RollbackManager::RollbackManager(const std::string & world_path, + IGameDef * gamedef_) : + gamedef(gamedef_), + current_actor_is_guess(false) { - // Suspect cannot cause things in the past - if(action_t < suspect_t) - return 0; // 0 = cannot be - // Start from 100 - int f = 100; - // Distance (1 node = +1 point) - f += 1.0 * intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1)); - // Time (1 second = -1 point) - f -= 1.0 * (action_t - suspect_t); - // If is a guess, halve the points - if(is_guess) - f *= 0.5; - // Limit to 0 - if(f < 0) - f = 0; - return f; + verbosestream << "RollbackManager::RollbackManager(" << world_path + << ")" << std::endl; + + std::string txt_filename = world_path + DIR_DELIM "rollback.txt"; + std::string migrating_flag = txt_filename + ".migrating"; + database_path = world_path + DIR_DELIM "rollback.sqlite"; + + initDatabase(); + + if (fs::PathExists(txt_filename) && (fs::PathExists(migrating_flag) || + !fs::PathExists(database_path))) { + std::ofstream of(migrating_flag.c_str()); + of.close(); + migrate(txt_filename); + fs::DeleteSingleFileOrEmptyDirectory(migrating_flag); + } } -class RollbackManager: public IRollbackManager + +RollbackManager::~RollbackManager() { -public: - // IRollbackManager interface + SQLOK(sqlite3_finalize(stmt_insert)); + SQLOK(sqlite3_finalize(stmt_replace)); + SQLOK(sqlite3_finalize(stmt_select)); + SQLOK(sqlite3_finalize(stmt_select_range)); + SQLOK(sqlite3_finalize(stmt_select_withActor)); + SQLOK(sqlite3_finalize(stmt_knownActor_select)); + SQLOK(sqlite3_finalize(stmt_knownActor_insert)); + SQLOK(sqlite3_finalize(stmt_knownNode_select)); + SQLOK(sqlite3_finalize(stmt_knownNode_insert)); - void reportAction(const RollbackAction &action_) - { - // Ignore if not important - if(!action_.isImportant(m_gamedef)) - return; - RollbackAction action = action_; - action.unix_time = time(0); - // Figure out actor - action.actor = m_current_actor; - action.actor_is_guess = m_current_actor_is_guess; - // If actor is not known, find out suspect or cancel - if(action.actor.empty()){ - v3s16 p; - if(!action.getPosition(&p)) - return; - action.actor = getSuspect(p, 5); // 5s timeframe - if(action.actor.empty()) - return; - action.actor_is_guess = true; + SQLOK(sqlite3_close(db)); +} + + +void RollbackManager::registerNewActor(const int id, const std::string &name) +{ + Entity actor = {id, name}; + knownActors.push_back(actor); +} + + +void RollbackManager::registerNewNode(const int id, const std::string &name) +{ + Entity node = {id, name}; + knownNodes.push_back(node); +} + + +int RollbackManager::getActorId(const std::string &name) +{ + for (std::vector::const_iterator iter = knownActors.begin(); + iter != knownActors.end(); ++iter) { + if (iter->name == name) { + return iter->id; } - infostream<<"RollbackManager::reportAction():" - <<" time="<::const_iterator iter = knownNodes.begin(); + iter != knownNodes.end(); ++iter) { + if (iter->id == id) { + return iter->name.c_str(); + } } - std::string getSuspect(v3s16 p, int max_time) - { - if(m_current_actor != "") - return m_current_actor; - int cur_time = time(0); - int first_time = cur_time - max_time; - RollbackAction likely_suspect; - float likely_suspect_nearness = 0; - for(std::list::const_reverse_iterator - i = m_action_latest_buffer.rbegin(); - i != m_action_latest_buffer.rend(); i++) - { - if(i->unix_time < first_time) - break; - // Find position of suspect or continue - v3s16 suspect_p; - if(!i->getPosition(&suspect_p)) - continue; - float f = getSuspectNearness(i->actor_is_guess, suspect_p, - i->unix_time, p, cur_time); - if(f > likely_suspect_nearness){ - likely_suspect_nearness = f; - likely_suspect = *i; - } + + return ""; +} + + +bool RollbackManager::createTables() +{ + SQLOK(sqlite3_exec(db, + "CREATE TABLE IF NOT EXISTS `actor` (\n" + " `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" + " `name` TEXT NOT NULL\n" + ");\n" + "CREATE TABLE IF NOT EXISTS `node` (\n" + " `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" + " `name` TEXT NOT NULL\n" + ");\n" + "CREATE TABLE IF NOT EXISTS `action` (\n" + " `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n" + " `actor` INTEGER NOT NULL,\n" + " `timestamp` TIMESTAMP NOT NULL,\n" + " `type` INTEGER NOT NULL,\n" + " `list` TEXT,\n" + " `index` INTEGER,\n" + " `add` INTEGER,\n" + " `stackNode` INTEGER,\n" + " `stackQuantity` INTEGER,\n" + " `nodeMeta` INTEGER,\n" + " `x` INT,\n" + " `y` INT,\n" + " `z` INT,\n" + " `oldNode` INTEGER,\n" + " `oldParam1` INTEGER,\n" + " `oldParam2` INTEGER,\n" + " `oldMeta` TEXT,\n" + " `newNode` INTEGER,\n" + " `newParam1` INTEGER,\n" + " `newParam2` INTEGER,\n" + " `newMeta` TEXT,\n" + " `guessedActor` INTEGER,\n" + " FOREIGN KEY (`actor`) REFERENCES `actor`(`id`),\n" + " FOREIGN KEY (`stackNode`) REFERENCES `node`(`id`),\n" + " FOREIGN KEY (`oldNode`) REFERENCES `node`(`id`),\n" + " FOREIGN KEY (`newNode`) REFERENCES `node`(`id`)\n" + ");\n" + "CREATE INDEX IF NOT EXISTS `actionActor` ON `action`(`actor`);\n" + "CREATE INDEX IF NOT EXISTS `actionTimestamp` ON `action`(`timestamp`);\n", + NULL, NULL, NULL)); + verbosestream << "SQL Rollback: SQLite3 database structure was created" << std::endl; + + return true; +} + + +void RollbackManager::initDatabase() +{ + verbosestream << "RollbackManager: Database connection setup" << std::endl; + + bool needsCreate = !fs::PathExists(database_path); + SQLOK(sqlite3_open_v2(database_path.c_str(), &db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)); + + if (needsCreate) { + createTables(); + } + + SQLOK(sqlite3_prepare_v2(db, + "INSERT INTO `action` (\n" + " `actor`, `timestamp`, `type`,\n" + " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodeMeta`,\n" + " `x`, `y`, `z`,\n" + " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" + " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" + " `guessedActor`\n" + ") VALUES (\n" + " ?, ?, ?,\n" + " ?, ?, ?, ?, ?, ?,\n" + " ?, ?, ?,\n" + " ?, ?, ?, ?,\n" + " ?, ?, ?, ?,\n" + " ?" + ");", + -1, &stmt_insert, NULL)); + + SQLOK(sqlite3_prepare_v2(db, + "REPLACE INTO `action` (\n" + " `actor`, `timestamp`, `type`,\n" + " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodeMeta`,\n" + " `x`, `y`, `z`,\n" + " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" + " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" + " `guessedActor`, `id`\n" + ") VALUES (\n" + " ?, ?, ?,\n" + " ?, ?, ?, ?, ?, ?,\n" + " ?, ?, ?,\n" + " ?, ?, ?, ?,\n" + " ?, ?, ?, ?,\n" + " ?, ?\n" + ");", + -1, &stmt_replace, NULL)); + + SQLOK(sqlite3_prepare_v2(db, + "SELECT\n" + " `actor`, `timestamp`, `type`,\n" + " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n" + " `x`, `y`, `z`,\n" + " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" + " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" + " `guessedActor`\n" + " FROM `action`\n" + " WHERE `timestamp` >= ?\n" + " ORDER BY `timestamp` DESC, `id` DESC", + -1, &stmt_select, NULL)); + + SQLOK(sqlite3_prepare_v2(db, + "SELECT\n" + " `actor`, `timestamp`, `type`,\n" + " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n" + " `x`, `y`, `z`,\n" + " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" + " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" + " `guessedActor`\n" + "FROM `action`\n" + "WHERE `timestamp` >= ?\n" + " AND `x` IS NOT NULL\n" + " AND `y` IS NOT NULL\n" + " AND `z` IS NOT NULL\n" + " AND `x` BETWEEN ? AND ?\n" + " AND `y` BETWEEN ? AND ?\n" + " AND `z` BETWEEN ? AND ?\n" + "ORDER BY `timestamp` DESC, `id` DESC\n" + "LIMIT 0,?", + -1, &stmt_select_range, NULL)); + + SQLOK(sqlite3_prepare_v2(db, + "SELECT\n" + " `actor`, `timestamp`, `type`,\n" + " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n" + " `x`, `y`, `z`,\n" + " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n" + " `newNode`, `newParam1`, `newParam2`, `newMeta`,\n" + " `guessedActor`\n" + "FROM `action`\n" + "WHERE `timestamp` >= ?\n" + " AND `actor` = ?\n" + "ORDER BY `timestamp` DESC, `id` DESC\n", + -1, &stmt_select_withActor, NULL)); + + SQLOK(sqlite3_prepare_v2(db, "SELECT `id`, `name` FROM `actor`", + -1, &stmt_knownActor_select, NULL)); + + SQLOK(sqlite3_prepare_v2(db, "INSERT INTO `actor` (`name`) VALUES (?)", + -1, &stmt_knownActor_insert, NULL)); + + SQLOK(sqlite3_prepare_v2(db, "SELECT `id`, `name` FROM `node`", + -1, &stmt_knownNode_select, NULL)); + + SQLOK(sqlite3_prepare_v2(db, "INSERT INTO `node` (`name`) VALUES (?)", + -1, &stmt_knownNode_insert, NULL)); + + verbosestream << "SQL prepared statements setup correctly" << std::endl; + + while (sqlite3_step(stmt_knownActor_select) == SQLITE_ROW) { + registerNewActor( + sqlite3_column_int(stmt_knownActor_select, 0), + reinterpret_cast(sqlite3_column_text(stmt_knownActor_select, 1)) + ); + } + SQLOK(sqlite3_reset(stmt_knownActor_select)); + + while (sqlite3_step(stmt_knownNode_select) == SQLITE_ROW) { + registerNewNode( + sqlite3_column_int(stmt_knownNode_select, 0), + reinterpret_cast(sqlite3_column_text(stmt_knownNode_select, 1)) + ); + } + SQLOK(sqlite3_reset(stmt_knownNode_select)); +} + + +bool RollbackManager::registerRow(const ActionRow & row) +{ + sqlite3_stmt * stmt_do = (row.id) ? stmt_replace : stmt_insert; + + bool nodeMeta = false; + + SQLOK(sqlite3_bind_int (stmt_do, 1, row.actor)); + SQLOK(sqlite3_bind_int64(stmt_do, 2, row.timestamp)); + SQLOK(sqlite3_bind_int (stmt_do, 3, row.type)); + + if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { + const std::string & loc = row.location; + nodeMeta = (loc.substr(0, 9) == "nodemeta:"); + + SQLOK(sqlite3_bind_text(stmt_do, 4, row.list.c_str(), row.list.size(), NULL)); + SQLOK(sqlite3_bind_int (stmt_do, 5, row.index)); + SQLOK(sqlite3_bind_int (stmt_do, 6, row.add)); + SQLOK(sqlite3_bind_int (stmt_do, 7, row.stack.id)); + SQLOK(sqlite3_bind_int (stmt_do, 8, row.stack.count)); + SQLOK(sqlite3_bind_int (stmt_do, 9, (int) nodeMeta)); + + if (nodeMeta) { + std::string::size_type p1, p2; + p1 = loc.find(':') + 1; + p2 = loc.find(','); + std::string x = loc.substr(p1, p2 - p1); + p1 = p2 + 1; + p2 = loc.find(',', p1); + std::string y = loc.substr(p1, p2 - p1); + std::string z = loc.substr(p2 + 1); + SQLOK(sqlite3_bind_int(stmt_do, 10, atoi(x.c_str()))); + SQLOK(sqlite3_bind_int(stmt_do, 11, atoi(y.c_str()))); + SQLOK(sqlite3_bind_int(stmt_do, 12, atoi(z.c_str()))); } - // No likely suspect was found - if(likely_suspect_nearness == 0) - return ""; - // Likely suspect was found - return likely_suspect.actor; + } else { + SQLOK(sqlite3_bind_null(stmt_do, 4)); + SQLOK(sqlite3_bind_null(stmt_do, 5)); + SQLOK(sqlite3_bind_null(stmt_do, 6)); + SQLOK(sqlite3_bind_null(stmt_do, 7)); + SQLOK(sqlite3_bind_null(stmt_do, 8)); + SQLOK(sqlite3_bind_null(stmt_do, 9)); } - void flush() - { - infostream<<"RollbackManager::flush()"< RollbackManager::actionRowsFromSelect(sqlite3_stmt* stmt) +{ + std::list rows; + const unsigned char * text; + size_t size; + + while (sqlite3_step(stmt) == SQLITE_ROW) { + ActionRow row; + + row.actor = sqlite3_column_int (stmt, 0); + row.timestamp = sqlite3_column_int64(stmt, 1); + row.type = sqlite3_column_int (stmt, 2); + + if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { + text = sqlite3_column_text (stmt, 3); + size = sqlite3_column_bytes(stmt, 3); + row.list = std::string(reinterpret_cast(text), size); + row.index = sqlite3_column_int(stmt, 4); + row.add = sqlite3_column_int(stmt, 5); + row.stack.id = sqlite3_column_int(stmt, 6); + row.stack.count = sqlite3_column_int(stmt, 7); + row.nodeMeta = sqlite3_column_int(stmt, 8); + } + + if (row.type == RollbackAction::TYPE_SET_NODE || row.nodeMeta) { + row.x = sqlite3_column_int(stmt, 9); + row.y = sqlite3_column_int(stmt, 10); + row.z = sqlite3_column_int(stmt, 11); } - for(std::list::const_iterator - i = m_action_todisk_buffer.begin(); - i != m_action_todisk_buffer.end(); i++) - { - // Do not save stuff that does not have an actor - if(i->actor == "") - continue; - of<unix_time; - of<<" "; - of<actor); - of<<" "; - of<toString(); - if(i->actor_is_guess){ - of<<" "; - of<<"actor_is_guess"; + + if (row.type == RollbackAction::TYPE_SET_NODE) { + row.oldNode = sqlite3_column_int(stmt, 12); + row.oldParam1 = sqlite3_column_int(stmt, 13); + row.oldParam2 = sqlite3_column_int(stmt, 14); + text = sqlite3_column_text (stmt, 15); + size = sqlite3_column_bytes(stmt, 15); + row.oldMeta = std::string(reinterpret_cast(text), size); + row.newNode = sqlite3_column_int(stmt, 16); + row.newParam1 = sqlite3_column_int(stmt, 17); + row.newParam2 = sqlite3_column_int(stmt, 18); + text = sqlite3_column_text(stmt, 19); + size = sqlite3_column_bytes(stmt, 19); + row.newMeta = std::string(reinterpret_cast(text), size); + row.guessed = sqlite3_column_int(stmt, 20); + } + + if (row.nodeMeta) { + row.location = "nodemeta:"; + row.location += itos(row.x); + row.location += ','; + row.location += itos(row.y); + row.location += ','; + row.location += itos(row.z); + } else { + row.location = getActorName(row.actor); + } + + rows.push_back(row); + } + + SQLOK(sqlite3_reset(stmt)); + + return rows; +} + + +ActionRow RollbackManager::actionRowFromRollbackAction(const RollbackAction & action) +{ + ActionRow row; + + row.id = 0; + row.actor = getActorId(action.actor); + row.timestamp = action.unix_time; + row.type = action.type; + + if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { + row.location = action.inventory_location; + row.list = action.inventory_list; + row.index = action.inventory_index; + row.add = action.inventory_add; + row.stack = action.inventory_stack; + row.stack.id = getNodeId(row.stack.name); + } else { + row.x = action.p.X; + row.y = action.p.Y; + row.z = action.p.Z; + row.oldNode = getNodeId(action.n_old.name); + row.oldParam1 = action.n_old.param1; + row.oldParam2 = action.n_old.param2; + row.oldMeta = action.n_old.meta; + row.newNode = getNodeId(action.n_new.name); + row.newParam1 = action.n_new.param1; + row.newParam2 = action.n_new.param2; + row.newMeta = action.n_new.meta; + row.guessed = action.actor_is_guess; + } + + return row; +} + + +const std::list RollbackManager::rollbackActionsFromActionRows( + const std::list & rows) +{ + std::list actions; + + for (std::list::const_iterator it = rows.begin(); + it != rows.end(); ++it) { + RollbackAction action; + action.actor = (it->actor) ? getActorName(it->actor) : ""; + action.unix_time = it->timestamp; + action.type = static_cast(it->type); + + switch (action.type) { + case RollbackAction::TYPE_MODIFY_INVENTORY_STACK: + action.inventory_location = it->location.c_str(); + action.inventory_list = it->list; + action.inventory_index = it->index; + action.inventory_add = it->add; + action.inventory_stack = it->stack; + if (action.inventory_stack.name.empty()) { + action.inventory_stack.name = getNodeName(it->stack.id); } - of<x, it->y, it->z); + action.n_old.name = getNodeName(it->oldNode); + action.n_old.param1 = it->oldParam1; + action.n_old.param2 = it->oldParam2; + action.n_old.meta = it->oldMeta; + action.n_new.name = getNodeName(it->newNode); + action.n_new.param1 = it->newParam1; + action.n_new.param2 = it->newParam2; + action.n_new.meta = it->newMeta; + break; + + default: + throw ("W.T.F."); + break; } - m_action_todisk_buffer.clear(); + + actions.push_back(action); } - - // Other - RollbackManager(const std::string &filepath, IGameDef *gamedef): - m_filepath(filepath), - m_gamedef(gamedef), - m_current_actor_is_guess(false) - { - infostream<<"RollbackManager::RollbackManager("< RollbackManager::getRowsSince(time_t firstTime, const std::string & actor) +{ + sqlite3_stmt *stmt_stmt = actor.empty() ? stmt_select : stmt_select_withActor; + sqlite3_bind_int64(stmt_stmt, 1, firstTime); + + if (!actor.empty()) { + sqlite3_bind_int(stmt_stmt, 2, getActorId(actor)); } - ~RollbackManager() - { - infostream<<"RollbackManager::~RollbackManager()"< & rows = actionRowsFromSelect(stmt_stmt); + sqlite3_reset(stmt_stmt); + + return rows; +} + + +const std::list RollbackManager::getRowsSince_range( + time_t start_time, v3s16 p, int range, int limit) +{ + + sqlite3_bind_int64(stmt_select_range, 1, start_time); + sqlite3_bind_int (stmt_select_range, 2, static_cast(p.X - range)); + sqlite3_bind_int (stmt_select_range, 3, static_cast(p.X + range)); + sqlite3_bind_int (stmt_select_range, 4, static_cast(p.Y - range)); + sqlite3_bind_int (stmt_select_range, 5, static_cast(p.Y + range)); + sqlite3_bind_int (stmt_select_range, 6, static_cast(p.Z - range)); + sqlite3_bind_int (stmt_select_range, 7, static_cast(p.Z + range)); + sqlite3_bind_int (stmt_select_range, 8, limit); + + const std::list & rows = actionRowsFromSelect(stmt_select_range); + sqlite3_reset(stmt_select_range); + + return rows; +} + + +const std::list RollbackManager::getActionsSince_range( + time_t start_time, v3s16 p, int range, int limit) +{ + return rollbackActionsFromActionRows(getRowsSince_range(start_time, p, range, limit)); +} + + +const std::list RollbackManager::getActionsSince( + time_t start_time, const std::string & actor) +{ + return rollbackActionsFromActionRows(getRowsSince(start_time, actor)); +} + + +void RollbackManager::migrate(const std::string & file_path) +{ + std::cout << "Migrating from rollback.txt to rollback.sqlite." << std::endl; + + std::ifstream fh(file_path.c_str(), std::ios::in | std::ios::ate); + if (!fh.good()) { + throw FileNotGoodException("Unable to open rollback.txt"); } - void addAction(const RollbackAction &action) - { - m_action_todisk_buffer.push_back(action); - m_action_latest_buffer.push_back(action); + std::streampos file_size = fh.tellg(); - // Flush to disk sometimes - if(m_action_todisk_buffer.size() >= 100) - flush(); + if (file_size > 10) { + errorstream << "Empty rollback log." << std::endl; + return; } - - bool readFile(std::list &dst) - { - // Load whole file to memory - std::ifstream f(m_filepath.c_str(), std::ios::in); - if(!f.good()){ - errorstream<<"RollbackManager::readFile(): Could not open " - <<"file for reading: \""<= 1) { + sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); + t = time(0); + std::cout + << " Done: " << static_cast((static_cast(fh.tellg()) / static_cast(file_size)) * 100) << "%" + << " Speed: " << i / (t - start) << "/second \r" << std::flush; + sqlite3_exec(db, "BEGIN", NULL, NULL, NULL); + } + } while (fh.good()); + + std::cout + << " Done: 100% " << std::endl + << "Now you can delete the old rollback.txt file." << std::endl; +} + + +// Get nearness factor for subject's action for this action +// Return value: 0 = impossible, >0 = factor +float RollbackManager::getSuspectNearness(bool is_guess, v3s16 suspect_p, + time_t suspect_t, v3s16 action_p, time_t action_t) +{ + // Suspect cannot cause things in the past + if (action_t < suspect_t) { + return 0; // 0 = cannot be } + // Start from 100 + int f = 100; + // Distance (1 node = -x points) + f -= POINTS_PER_NODE * intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1)); + // Time (1 second = -x points) + f -= 1 * (action_t - suspect_t); + // If is a guess, halve the points + if (is_guess) { + f *= 0.5; + } + // Limit to 0 + if (f < 0) { + f = 0; + } + return f; +} - std::list getEntriesSince(int first_time) - { - infostream<<"RollbackManager::getEntriesSince("< action_buffer; - // Use the latest buffer if it is long enough - if(!m_action_latest_buffer.empty() && - m_action_latest_buffer.begin()->unix_time <= first_time){ - action_buffer = m_action_latest_buffer; + +void RollbackManager::reportAction(const RollbackAction &action_) +{ + // Ignore if not important + if (!action_.isImportant(gamedef)) { + return; + } + + RollbackAction action = action_; + action.unix_time = time(0); + + // Figure out actor + action.actor = current_actor; + action.actor_is_guess = current_actor_is_guess; + + if (action.actor.empty()) { // If actor is not known, find out suspect or cancel + v3s16 p; + if (!action.getPosition(&p)) { + return; } - else - { - // Save all remaining stuff - flush(); - // Load whole file to memory - bool good = readFile(action_buffer); - if(!good){ - errorstream<<"RollbackManager::getEntriesSince(): Failed to" - <<" open file; using data in memory."< action_buffer = getEntriesSince(first_time); - - std::list result; - - for(std::list::const_reverse_iterator - i = action_buffer.rbegin(); - i != action_buffer.rend(); i++) - { - if(i->unix_time < first_time) - break; - // Find position of action or continue - v3s16 action_p; - if(!i->getPosition(&action_p)) - continue; - - if(range == 0){ - if(action_p != p) - continue; - } else { - if(abs(action_p.X - p.X) > range || - abs(action_p.Y - p.Y) > range || - abs(action_p.Z - p.Z) > range) - continue; + addAction(action); +} + +std::string RollbackManager::getActor() +{ + return current_actor; +} + +bool RollbackManager::isActorGuess() +{ + return current_actor_is_guess; +} + +void RollbackManager::setActor(const std::string & actor, bool is_guess) +{ + current_actor = actor; + current_actor_is_guess = is_guess; +} + +std::string RollbackManager::getSuspect(v3s16 p, float nearness_shortcut, + float min_nearness) +{ + if (current_actor != "") { + return current_actor; + } + int cur_time = time(0); + time_t first_time = cur_time - (100 - min_nearness); + RollbackAction likely_suspect; + float likely_suspect_nearness = 0; + for (std::list::const_reverse_iterator + i = action_latest_buffer.rbegin(); + i != action_latest_buffer.rend(); i++) { + if (i->unix_time < first_time) { + break; + } + if (i->actor == "") { + continue; + } + // Find position of suspect or continue + v3s16 suspect_p; + if (!i->getPosition(&suspect_p)) { + continue; + } + float f = getSuspectNearness(i->actor_is_guess, suspect_p, + i->unix_time, p, cur_time); + if (f >= min_nearness && f > likely_suspect_nearness) { + likely_suspect_nearness = f; + likely_suspect = *i; + if (likely_suspect_nearness >= nearness_shortcut) { + break; } - - if(act_p) - *act_p = action_p; - if(act_seconds) - *act_seconds = cur_time - i->unix_time; - return i->actor; } + } + // No likely suspect was found + if (likely_suspect_nearness == 0) { return ""; } + // Likely suspect was found + return likely_suspect.actor; +} - std::list getRevertActions(const std::string &actor_filter, - int seconds) - { - infostream<<"RollbackManager::getRevertActions("< action_buffer = getEntriesSince(first_time); - - std::list result; - - for(std::list::const_reverse_iterator - i = action_buffer.rbegin(); - i != action_buffer.rend(); i++) - { - if(i->unix_time < first_time) - break; - if(i->actor != actor_filter) - continue; - const RollbackAction &action = *i; - /*infostream<<"RollbackManager::revertAction(): Should revert" - <<" time="<::const_iterator iter; + + for (iter = action_todisk_buffer.begin(); + iter != action_todisk_buffer.end(); + iter++) { + if (iter->actor == "") { + continue; } - return result; + registerRow(actionRowFromRollbackAction(*iter)); } -private: - std::string m_filepath; - IGameDef *m_gamedef; - std::string m_current_actor; - bool m_current_actor_is_guess; - std::list m_action_todisk_buffer; - std::list m_action_latest_buffer; -}; + sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); + action_todisk_buffer.clear(); +} + + +void RollbackManager::addAction(const RollbackAction & action) +{ + action_todisk_buffer.push_back(action); + action_latest_buffer.push_back(action); + + // Flush to disk sometimes + if (action_todisk_buffer.size() >= 500) { + flush(); + } +} -IRollbackManager *createRollbackManager(const std::string &filepath, IGameDef *gamedef) +std::list RollbackManager::getEntriesSince(time_t first_time) { - return new RollbackManager(filepath, gamedef); + flush(); + return getActionsSince(first_time); } +std::list RollbackManager::getNodeActors(v3s16 pos, int range, + time_t seconds, int limit) +{ + time_t cur_time = time(0); + time_t first_time = cur_time - seconds; + + return getActionsSince_range(first_time, pos, range, limit); +} + +std::list RollbackManager::getRevertActions( + const std::string &actor_filter, + time_t seconds) +{ + time_t cur_time = time(0); + time_t first_time = cur_time - seconds; + + flush(); + + return getActionsSince(first_time, actor_filter); +}