/* 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 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 "rollback.h" #include #include #include #include "log.h" #include "mapnode.h" #include "gamedef.h" #include "nodedef.h" #include "util/serialize.h" #include "util/string.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) std::string dbp; sqlite3* dbh; sqlite3_stmt* dbs_insert; sqlite3_stmt* dbs_replace; sqlite3_stmt* dbs_select; sqlite3_stmt* dbs_select_range; sqlite3_stmt* dbs_select_withActor; sqlite3_stmt* dbs_knownActor_select; sqlite3_stmt* dbs_knownActor_insert; sqlite3_stmt* dbs_knownNode_select; sqlite3_stmt* dbs_knownNode_insert; struct Stack { int node; int quantity; }; struct ActionRow { int id; int actor; time_t timestamp; int type; std::string location, list; int index, add; Stack 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; }; typedef std::vector Entities; Entities KnownActors; Entities KnownNodes; void registerNewActor(int id, std::string name) { Entity newActor; newActor.id = id; newActor.name = name; KnownActors.push_back(newActor); //std::cout << "New actor registered: " << id << " | " << name << std::endl; } void registerNewNode(int id, std::string name) { Entity newNode; newNode.id = id; newNode.name = name; KnownNodes.push_back(newNode); //std::cout << "New node registered: " << id << " | " << name << std::endl; } int getActorId(std::string name) { Entities::const_iterator iter; for (iter = KnownActors.begin(); iter != KnownActors.end(); ++iter) if (iter->name == name) { return iter->id; } sqlite3_reset(dbs_knownActor_insert); sqlite3_bind_text(dbs_knownActor_insert, 1, name.c_str(), -1, NULL); sqlite3_step(dbs_knownActor_insert); int id = sqlite3_last_insert_rowid(dbh); //std::cout << "Actor ID insert returns " << insert << std::endl; registerNewActor(id, name); return id; } int getNodeId(std::string name) { Entities::const_iterator iter; for (iter = KnownNodes.begin(); iter != KnownNodes.end(); ++iter) if (iter->name == name) { return iter->id; } sqlite3_reset(dbs_knownNode_insert); sqlite3_bind_text(dbs_knownNode_insert, 1, name.c_str(), -1, NULL); sqlite3_step(dbs_knownNode_insert); int id = sqlite3_last_insert_rowid(dbh); registerNewNode(id, name); return id; } const char * getActorName(int id) { Entities::const_iterator iter; //std::cout << "getActorName of id " << id << std::endl; for (iter = KnownActors.begin(); iter != KnownActors.end(); ++iter) if (iter->id == id) { return iter->name.c_str(); } return ""; } const char * getNodeName(int id) { Entities::const_iterator iter; //std::cout << "getNodeName of id " << id << std::endl; for (iter = KnownNodes.begin(); iter != KnownNodes.end(); ++iter) if (iter->id == id) { return iter->name.c_str(); } return ""; } Stack getStackFromString(std::string text) { Stack stack; size_t off = text.find_last_of(" "); stack.node = getNodeId(text.substr(0, off)); stack.quantity = atoi(text.substr(off + 1).c_str()); return stack; } std::string getStringFromStack(Stack stack) { std::string text; text.append(getNodeName(stack.node)); text.append(" "); text.append(itos(stack.quantity)); return text; } bool SQL_createDatabase(void) { infostream << "CreateDB:" << dbp << std::endl; int dbs = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS `actor` (" "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," "`name` TEXT NOT NULL);" "CREATE TABLE IF NOT EXISTS `node` (" "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," "`name` TEXT NOT NULL);" "CREATE TABLE IF NOT EXISTS `action` (" "`id` INTEGER PRIMARY KEY AUTOINCREMENT," "`actor` INTEGER NOT NULL," "`timestamp` TIMESTAMP NOT NULL," "`type` INTEGER NOT NULL," "`list` TEXT," "`index` INTEGER," "`add` INTEGER," "`stackNode` INTEGER," "`stackQuantity` INTEGER," "`nodeMeta` INTEGER," "`x` INT," "`y` INT," "`z` INT," "`oldNode` INTEGER," "`oldParam1` INTEGER," "`oldParam2` INTEGER," "`oldMeta` TEXT," "`newNode` INTEGER," "`newParam1` INTEGER," "`newParam2` INTEGER," "`newMeta` TEXT," "`guessedActor` INTEGER," "FOREIGN KEY (`actor`) REFERENCES `actor`(`id`)," "FOREIGN KEY (`oldNode`) REFERENCES `node`(`id`)," "FOREIGN KEY (`newNode`) REFERENCES `node`(`id`));" "CREATE INDEX IF NOT EXISTS `actionActor` ON `action`(`actor`);" "CREATE INDEX IF NOT EXISTS `actionTimestamp` ON `action`(`timestamp`);", NULL, NULL, NULL); if (dbs == SQLITE_ABORT) { throw FileNotGoodException("Could not create sqlite3 database structure"); } else if (dbs != 0) { throw FileNotGoodException("SQL Rollback: Exec statement to create table structure returned a non-zero value"); } else { infostream << "SQL Rollback: SQLite3 database structure was created" << std::endl; } return true; } void SQL_databaseCheck(void) { if (dbh) { return; } infostream << "Database connection setup" << std::endl; bool needsCreate = !fs::PathExists(dbp); int dbo = sqlite3_open_v2(dbp.c_str(), &dbh, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); if (dbo != SQLITE_OK) { infostream << "SQLROLLBACK: SQLite3 database failed to open: " << sqlite3_errmsg(dbh) << std::endl; throw FileNotGoodException("Cannot open database file"); } if (needsCreate) { SQL_createDatabase(); } int dbr; dbr = sqlite3_prepare_v2(dbh, "INSERT INTO `action` (" " `actor`, `timestamp`, `type`," " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodeMeta`," " `x`, `y`, `z`," " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`," " `newNode`, `newParam1`, `newParam2`, `newMeta`," " `guessedActor`" ") VALUES (" " ?, ?, ?," " ?, ?, ?, ?, ?, ?," " ?, ?, ?," " ?, ?, ?, ?," " ?, ?, ?, ?," " ?" ");", -1, &dbs_insert, NULL); if (dbr != SQLITE_OK) { throw FileNotGoodException(sqlite3_errmsg(dbh)); } dbr = sqlite3_prepare_v2(dbh, "REPLACE INTO `action` (" " `actor`, `timestamp`, `type`," " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodeMeta`," " `x`, `y`, `z`," " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`," " `newNode`, `newParam1`, `newParam2`, `newMeta`," " `guessedActor`, `id`" ") VALUES (" " ?, ?, ?," " ?, ?, ?, ?, ?, ?," " ?, ?, ?," " ?, ?, ?, ?," " ?, ?, ?, ?," " ?, ?" ");", -1, &dbs_replace, NULL); if (dbr != SQLITE_OK) { throw FileNotGoodException(sqlite3_errmsg(dbh)); } dbr = sqlite3_prepare_v2(dbh, "SELECT" " `actor`, `timestamp`, `type`," " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`," " `x`, `y`, `z`," " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`," " `newNode`, `newParam1`, `newParam2`, `newMeta`," " `guessedActor`" " FROM `action`" " WHERE `timestamp` >= ?" " ORDER BY `timestamp` DESC, `id` DESC", -1, &dbs_select, NULL); if (dbr != SQLITE_OK) { throw FileNotGoodException(itos(dbr).c_str()); } dbr = sqlite3_prepare_v2(dbh, "SELECT" " `actor`, `timestamp`, `type`," " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`," " `x`, `y`, `z`," " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`," " `newNode`, `newParam1`, `newParam2`, `newMeta`," " `guessedActor`" " FROM `action`" " WHERE `timestamp` >= ?" " AND `x` IS NOT NULL" " AND `y` IS NOT NULL" " AND `z` IS NOT NULL" " AND ABS(`x` - ?) <= ?" " AND ABS(`y` - ?) <= ?" " AND ABS(`z` - ?) <= ?" " ORDER BY `timestamp` DESC, `id` DESC" " LIMIT 0,?", -1, &dbs_select_range, NULL); if (dbr != SQLITE_OK) { throw FileNotGoodException(itos(dbr).c_str()); } dbr = sqlite3_prepare_v2(dbh, "SELECT" " `actor`, `timestamp`, `type`," " `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`," " `x`, `y`, `z`," " `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`," " `newNode`, `newParam1`, `newParam2`, `newMeta`," " `guessedActor`" " FROM `action`" " WHERE `timestamp` >= ?" " AND `actor` = ?" " ORDER BY `timestamp` DESC, `id` DESC", -1, &dbs_select_withActor, NULL); if (dbr != SQLITE_OK) { throw FileNotGoodException(itos(dbr).c_str()); } dbr = sqlite3_prepare_v2(dbh, "SELECT `id`, `name` FROM `actor`", -1, &dbs_knownActor_select, NULL); if (dbr != SQLITE_OK) { throw FileNotGoodException(itos(dbr).c_str()); } dbr = sqlite3_prepare_v2(dbh, "INSERT INTO `actor` (`name`) VALUES (?)", -1, &dbs_knownActor_insert, NULL); if (dbr != SQLITE_OK) { throw FileNotGoodException(itos(dbr).c_str()); } dbr = sqlite3_prepare_v2(dbh, "SELECT `id`, `name` FROM `node`", -1, &dbs_knownNode_select, NULL); if (dbr != SQLITE_OK) { throw FileNotGoodException(itos(dbr).c_str()); } dbr = sqlite3_prepare_v2(dbh, "INSERT INTO `node` (`name`) VALUES (?)", -1, &dbs_knownNode_insert, NULL); if (dbr != SQLITE_OK) { throw FileNotGoodException(itos(dbr).c_str()); } infostream << "SQL prepared statements setup correctly" << std::endl; int select; sqlite3_reset(dbs_knownActor_select); while (SQLITE_ROW == (select = sqlite3_step(dbs_knownActor_select))) registerNewActor( sqlite3_column_int(dbs_knownActor_select, 0), reinterpret_cast(sqlite3_column_text(dbs_knownActor_select, 1)) ); sqlite3_reset(dbs_knownNode_select); while (SQLITE_ROW == (select = sqlite3_step(dbs_knownNode_select))) registerNewNode( sqlite3_column_int(dbs_knownNode_select, 0), reinterpret_cast(sqlite3_column_text(dbs_knownNode_select, 1)) ); return; } bool SQL_registerRow(ActionRow row) { SQL_databaseCheck(); sqlite3_stmt * dbs_do = (row.id) ? dbs_replace : dbs_insert; /* std::cout << (row.id? "Replacing": "Inserting") << " ActionRow" << std::endl; */ sqlite3_reset(dbs_do); int bind [22], ii = 0; bool nodeMeta = false; bind[ii++] = sqlite3_bind_int(dbs_do, 1, row.actor); bind[ii++] = sqlite3_bind_int64(dbs_do, 2, row.timestamp); bind[ii++] = sqlite3_bind_int(dbs_do, 3, row.type); if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { std::string loc = row.location; std::string locType = loc.substr(0, loc.find(":")); nodeMeta = (locType == "nodemeta"); bind[ii++] = sqlite3_bind_text(dbs_do, 4, row.list.c_str(), row.list.size(), NULL); bind[ii++] = sqlite3_bind_int(dbs_do, 5, row.index); bind[ii++] = sqlite3_bind_int(dbs_do, 6, row.add); bind[ii++] = sqlite3_bind_int(dbs_do, 7, row.stack.node); bind[ii++] = sqlite3_bind_int(dbs_do, 8, row.stack.quantity); bind[ii++] = sqlite3_bind_int(dbs_do, 9, (int) nodeMeta); if (nodeMeta) { std::string x, y, z; int l, r; l = loc.find(':') + 1; r = loc.find(','); x = loc.substr(l, r - l); l = r + 1; r = loc.find(',', l); y = loc.substr(l, r - l); z = loc.substr(r + 1); bind[ii++] = sqlite3_bind_int(dbs_do, 10, atoi(x.c_str())); bind[ii++] = sqlite3_bind_int(dbs_do, 11, atoi(y.c_str())); bind[ii++] = sqlite3_bind_int(dbs_do, 12, atoi(z.c_str())); } } else { bind[ii++] = sqlite3_bind_null(dbs_do, 4); bind[ii++] = sqlite3_bind_null(dbs_do, 5); bind[ii++] = sqlite3_bind_null(dbs_do, 6); bind[ii++] = sqlite3_bind_null(dbs_do, 7); bind[ii++] = sqlite3_bind_null(dbs_do, 8); bind[ii++] = sqlite3_bind_null(dbs_do, 9); } if (row.type == RollbackAction::TYPE_SET_NODE) { bind[ii++] = sqlite3_bind_int(dbs_do, 10, row.x); bind[ii++] = sqlite3_bind_int(dbs_do, 11, row.y); bind[ii++] = sqlite3_bind_int(dbs_do, 12, row.z); bind[ii++] = sqlite3_bind_int(dbs_do, 13, row.oldNode); bind[ii++] = sqlite3_bind_int(dbs_do, 14, row.oldParam1); bind[ii++] = sqlite3_bind_int(dbs_do, 15, row.oldParam2); bind[ii++] = sqlite3_bind_text(dbs_do, 16, row.oldMeta.c_str(), row.oldMeta.size(), NULL); bind[ii++] = sqlite3_bind_int(dbs_do, 17, row.newNode); bind[ii++] = sqlite3_bind_int(dbs_do, 18, row.newParam1); bind[ii++] = sqlite3_bind_int(dbs_do, 19, row.newParam2); bind[ii++] = sqlite3_bind_text(dbs_do, 20, row.newMeta.c_str(), row.newMeta.size(), NULL); bind[ii++] = sqlite3_bind_int(dbs_do, 21, row.guessed ? 1 : 0); } else { if (!nodeMeta) { bind[ii++] = sqlite3_bind_null(dbs_do, 10); bind[ii++] = sqlite3_bind_null(dbs_do, 11); bind[ii++] = sqlite3_bind_null(dbs_do, 12); } bind[ii++] = sqlite3_bind_null(dbs_do, 13); bind[ii++] = sqlite3_bind_null(dbs_do, 14); bind[ii++] = sqlite3_bind_null(dbs_do, 15); bind[ii++] = sqlite3_bind_null(dbs_do, 16); bind[ii++] = sqlite3_bind_null(dbs_do, 17); bind[ii++] = sqlite3_bind_null(dbs_do, 18); bind[ii++] = sqlite3_bind_null(dbs_do, 19); bind[ii++] = sqlite3_bind_null(dbs_do, 20); bind[ii++] = sqlite3_bind_null(dbs_do, 21); } if (row.id) { bind[ii++] = sqlite3_bind_int(dbs_do, 22, row.id); } for (ii = 0; ii < 20; ++ii) if (bind[ii] != SQLITE_OK) infostream << "WARNING: failed to bind param " << ii + 1 << " when inserting an entry in table setnode" << std::endl; /* std::cout << "========DB-WRITTEN==========" << std::endl; std::cout << "id: " << row.id << std::endl; std::cout << "actor: " << row.actor << std::endl; std::cout << "time: " << row.timestamp << std::endl; std::cout << "type: " << row.type << std::endl; if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { std::cout << "Location: " << row.location << std::endl; std::cout << "List: " << row.list << std::endl; std::cout << "Index: " << row.index << std::endl; std::cout << "Add: " << row.add << std::endl; std::cout << "Stack: " << row.stack << std::endl; } if (row.type == RollbackAction::TYPE_SET_NODE) { std::cout << "x: " << row.x << std::endl; std::cout << "y: " << row.y << std::endl; std::cout << "z: " << row.z << std::endl; std::cout << "oldNode: " << row.oldNode << std::endl; std::cout << "oldParam1: " << row.oldParam1 << std::endl; std::cout << "oldParam2: " << row.oldParam2 << std::endl; std::cout << "oldMeta: " << row.oldMeta << std::endl; std::cout << "newNode: " << row.newNode << std::endl; std::cout << "newParam1: " << row.newParam1 << std::endl; std::cout << "newParam2: " << row.newParam2 << std::endl; std::cout << "newMeta: " << row.newMeta << std::endl; std::cout << "DESERIALIZE" << row.newMeta.c_str() << std::endl; std::cout << "guessed: " << row.guessed << std::endl; } */ int written = sqlite3_step(dbs_do); return written == SQLITE_DONE; //if (written != SQLITE_DONE) // std::cout << "WARNING: rollback action not written: " << sqlite3_errmsg(dbh) << std::endl; //else std::cout << "Action correctly inserted via SQL" << std::endl; } std::list actionRowsFromSelect(sqlite3_stmt* stmt) { std::list rows; const unsigned char * text; size_t size; while (SQLITE_ROW == sqlite3_step(stmt)) { 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.node = sqlite3_column_int(stmt, 6); row.stack.quantity = 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); } 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); } row.location = row.nodeMeta ? "nodemeta:" : getActorName(row.actor); if (row.nodeMeta) { row.location.append(itos(row.x)); row.location.append(","); row.location.append(itos(row.y)); row.location.append(","); row.location.append(itos(row.z)); } /* std::cout << "=======SELECTED==========" << "\n"; std::cout << "Actor: " << row.actor << "\n"; std::cout << "Timestamp: " << row.timestamp << "\n"; if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { std::cout << "list: " << row.list << "\n"; std::cout << "index: " << row.index << "\n"; std::cout << "add: " << row.add << "\n"; std::cout << "stackNode: " << row.stack.node << "\n"; std::cout << "stackQuantity: " << row.stack.quantity << "\n"; if (row.nodeMeta) { std::cout << "X: " << row.x << "\n"; std::cout << "Y: " << row.y << "\n"; std::cout << "Z: " << row.z << "\n"; } std::cout << "Location: " << row.location << "\n"; } else { std::cout << "X: " << row.x << "\n"; std::cout << "Y: " << row.y << "\n"; std::cout << "Z: " << row.z << "\n"; std::cout << "oldNode: " << row.oldNode << "\n"; std::cout << "oldParam1: " << row.oldParam1 << "\n"; std::cout << "oldParam2: " << row.oldParam2 << "\n"; std::cout << "oldMeta: " << row.oldMeta << "\n"; std::cout << "newNode: " << row.newNode << "\n"; std::cout << "newParam1: " << row.newParam1 << "\n"; std::cout << "newParam2: " << row.newParam2 << "\n"; std::cout << "newMeta: " << row.newMeta << "\n"; std::cout << "guessed: " << row.guessed << "\n"; } */ rows.push_back(row); } return rows; } ActionRow actionRowFromRollbackAction(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 = getStackFromString(action.inventory_stack); } 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; } std::list rollbackActionsFromActionRows(std::list rows) { std::list actions; std::list::const_iterator it; for (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 = getStringFromStack(it->stack); break; case RollbackAction::TYPE_SET_NODE: action.p = v3s16(it->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; } actions.push_back(action); } return actions; } std::list SQL_getRowsSince(time_t firstTime, std::string actor = "") { sqlite3_stmt *dbs_stmt = (!actor.length()) ? dbs_select : dbs_select_withActor; sqlite3_reset(dbs_stmt); sqlite3_bind_int64(dbs_stmt, 1, firstTime); if (actor.length()) { sqlite3_bind_int(dbs_stmt, 2, getActorId(actor)); } return actionRowsFromSelect(dbs_stmt); } std::list SQL_getRowsSince_range(time_t firstTime, v3s16 p, int range, int limit) { sqlite3_stmt *stmt = dbs_select_range; sqlite3_reset(stmt); sqlite3_bind_int64(stmt, 1, firstTime); sqlite3_bind_int(stmt, 2, (int) p.X); sqlite3_bind_int(stmt, 3, range); sqlite3_bind_int(stmt, 4, (int) p.Y); sqlite3_bind_int(stmt, 5, range); sqlite3_bind_int(stmt, 6, (int) p.Z); sqlite3_bind_int(stmt, 7, range); sqlite3_bind_int(stmt, 8, limit); return actionRowsFromSelect(stmt); } std::list SQL_getActionsSince_range(time_t firstTime, v3s16 p, int range, int limit) { std::list rows = SQL_getRowsSince_range(firstTime, p, range, limit); return rollbackActionsFromActionRows(rows); } std::list SQL_getActionsSince(time_t firstTime, std::string actor = "") { std::list rows = SQL_getRowsSince(firstTime, actor); return rollbackActionsFromActionRows(rows); } void TXT_migrate(std::string filepath) { std::cout << "Migrating from rollback.txt to rollback.sqlite" << std::endl; SQL_databaseCheck(); std::ifstream fh(filepath.c_str(), std::ios::in | std::ios::ate); if (!fh.good()) { throw ("DIE"); } std::streampos filesize = fh.tellg(); if (filesize > 10) { fh.seekg(0); std::string bit; int i = 0; int id = 1; time_t t = 0; do { ActionRow row; row.id = id; // Get the timestamp std::getline(fh, bit, ' '); bit = trim(bit); if (!atoi(trim(bit).c_str())) { std::getline(fh, bit); continue; } row.timestamp = atoi(bit.c_str()); // Get the actor row.actor = getActorId(trim(deSerializeJsonString(fh))); // Get the action type std::getline(fh, bit, '['); std::getline(fh, bit, ' '); if (bit == "modify_inventory_stack") { row.type = RollbackAction::TYPE_MODIFY_INVENTORY_STACK; } if (bit == "set_node") { row.type = RollbackAction::TYPE_SET_NODE; } if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { row.location = trim(deSerializeJsonString(fh)); std::getline(fh, bit, ' '); row.list = trim(deSerializeJsonString(fh)); std::getline(fh, bit, ' '); std::getline(fh, bit, ' '); row.index = atoi(trim(bit).c_str()); std::getline(fh, bit, ' '); row.add = (int)(trim(bit) == "add"); row.stack = getStackFromString(trim(deSerializeJsonString(fh))); std::getline(fh, bit); } else if (row.type == RollbackAction::TYPE_SET_NODE) { std::getline(fh, bit, '('); std::getline(fh, bit, ','); row.x = atoi(trim(bit).c_str()); std::getline(fh, bit, ','); row.y = atoi(trim(bit).c_str()); std::getline(fh, bit, ')'); row.z = atoi(trim(bit).c_str()); std::getline(fh, bit, ' '); row.oldNode = getNodeId(trim(deSerializeJsonString(fh))); std::getline(fh, bit, ' '); std::getline(fh, bit, ' '); row.oldParam1 = atoi(trim(bit).c_str()); std::getline(fh, bit, ' '); row.oldParam2 = atoi(trim(bit).c_str()); row.oldMeta = trim(deSerializeJsonString(fh)); std::getline(fh, bit, ' '); row.newNode = getNodeId(trim(deSerializeJsonString(fh))); std::getline(fh, bit, ' '); std::getline(fh, bit, ' '); row.newParam1 = atoi(trim(bit).c_str()); std::getline(fh, bit, ' '); row.newParam2 = atoi(trim(bit).c_str()); row.newMeta = trim(deSerializeJsonString(fh)); std::getline(fh, bit, ' '); std::getline(fh, bit, ' '); std::getline(fh, bit); row.guessed = (int)(trim(bit) == "actor_is_guess"); } /* std::cout << "==========READ===========" << std::endl; std::cout << "time: " << row.timestamp << std::endl; std::cout << "actor: " << row.actor << std::endl; std::cout << "type: " << row.type << std::endl; if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) { std::cout << "Location: " << row.location << std::endl; std::cout << "List: " << row.list << std::endl; std::cout << "Index: " << row.index << std::endl; std::cout << "Add: " << row.add << std::endl; std::cout << "Stack: " << row.stack << std::endl; } if (row.type == RollbackAction::TYPE_SET_NODE) { std::cout << "x: " << row.x << std::endl; std::cout << "y: " << row.y << std::endl; std::cout << "z: " << row.z << std::endl; std::cout << "oldNode: " << row.oldNode << std::endl; std::cout << "oldParam1: " << row.oldParam1 << std::endl; std::cout << "oldParam2: " << row.oldParam2 << std::endl; std::cout << "oldMeta: " << row.oldMeta << std::endl; std::cout << "newNode: " << row.newNode << std::endl; std::cout << "newParam1: " << row.newParam1 << std::endl; std::cout << "newParam2: " << row.newParam2 << std::endl; std::cout << "newMeta: " << row.newMeta << std::endl; std::cout << "guessed: " << row.guessed << std::endl; } */ if (i == 0) { t = time(0); sqlite3_exec(dbh, "BEGIN", NULL, NULL, NULL); } SQL_registerRow(row); ++i; if (time(0) - t) { sqlite3_exec(dbh, "COMMIT", NULL, NULL, NULL); t = time(0) - t; std::cout << " Done: " << (int)(((float) fh.tellg() / (float) filesize) * 100) << "%" << " Speed: " << i / t << "/second \r" << std::flush; i = 0; } ++id; } while (!fh.eof() && fh.good()); } else { errorstream << "Empty rollback log" << std::endl; } 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 static float 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; } class RollbackManager: public IRollbackManager { public: // IRollbackManager interface 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 (action.actor.empty()) { // If actor is not known, find out suspect or cancel v3s16 p; if (!action.getPosition(&p)) { return; } action.actor = getSuspect(p, 83, 1); if (action.actor.empty()) { return; } action.actor_is_guess = true; } infostream << "RollbackManager::reportAction():" << " time=" << action.unix_time << " actor=\"" << action.actor << "\"" << (action.actor_is_guess ? " (guess)" : "") << " action=" << action.toString() << std::endl; addAction(action); } std::string getActor() { return m_current_actor; } bool isActorGuess() { return m_current_actor_is_guess; } void setActor(const std::string &actor, bool is_guess) { m_current_actor = actor; m_current_actor_is_guess = is_guess; } std::string getSuspect(v3s16 p, float nearness_shortcut, float min_nearness) { if (m_current_actor != "") { return m_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 = m_action_latest_buffer.rbegin(); i != m_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; } } } // No likely suspect was found if (likely_suspect_nearness == 0) { return ""; } // Likely suspect was found return likely_suspect.actor; } void flush() { infostream << "RollbackManager::flush()" << std::endl; sqlite3_exec(dbh, "BEGIN", NULL, NULL, NULL); std::list::const_iterator iter; for (iter = m_action_todisk_buffer.begin(); iter != m_action_todisk_buffer.end(); iter++) { if (iter->actor == "") { continue; } SQL_registerRow(actionRowFromRollbackAction(*iter)); } sqlite3_exec(dbh, "COMMIT", NULL, NULL, NULL); m_action_todisk_buffer.clear(); } RollbackManager(const std::string &filepath, IGameDef *gamedef): m_filepath(filepath), m_gamedef(gamedef), m_current_actor_is_guess(false) { infostream << "RollbackManager::RollbackManager(" << filepath << ")" << std::endl; // Operate correctly in case of still being given rollback.txt as filepath std::string directory = filepath.substr(0, filepath.rfind(DIR_DELIM) + 1); std::string filenameOld = filepath.substr(filepath.rfind(DIR_DELIM) + 1); std::string filenameNew = (filenameOld == "rollback.txt") ? "rollback.sqlite" : filenameOld; std::string filenameTXT = directory + "rollback.txt"; std::string migratingFlag = filepath + ".migrating"; infostream << "Directory: " << directory << std::endl; infostream << "CheckFor: " << filenameTXT << std::endl; infostream << "FileOld: " << filenameOld << std::endl; infostream << "FileNew: " << filenameNew << std::endl; dbp = directory + filenameNew; if ((fs::PathExists(filenameTXT) && fs::PathExists(migratingFlag)) || (fs::PathExists(filenameTXT) && !fs::PathExists(dbp))) { std::ofstream of(migratingFlag.c_str()); TXT_migrate(filenameTXT); fs::DeleteSingleFileOrEmptyDirectory(migratingFlag); } SQL_databaseCheck(); } ~RollbackManager() { infostream << "RollbackManager::~RollbackManager()" << std::endl; flush(); } void addAction(const RollbackAction &action) { m_action_todisk_buffer.push_back(action); m_action_latest_buffer.push_back(action); // Flush to disk sometimes if (m_action_todisk_buffer.size() >= 500) { flush(); } } std::list getEntriesSince(time_t first_time) { infostream << "RollbackManager::getEntriesSince(" << first_time << ")" << std::endl; flush(); std::list result = SQL_getActionsSince(first_time); return result; } std::list getNodeActors(v3s16 pos, int range, time_t seconds, int limit) { time_t cur_time = time(0); time_t first_time = cur_time - seconds; return SQL_getActionsSince_range(first_time, pos, range, limit); } std::list getRevertActions(const std::string &actor_filter, time_t seconds) { infostream << "RollbackManager::getRevertActions(" << actor_filter << ", " << seconds << ")" << std::endl; // Figure out time time_t cur_time = time(0); time_t first_time = cur_time - seconds; flush(); std::list result = SQL_getActionsSince(first_time, actor_filter); return result; } 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; }; IRollbackManager *createRollbackManager(const std::string &filepath, IGameDef *gamedef) { return new RollbackManager(filepath, gamedef); }