diff options
author | Mario Barrera <alley.marius@gmail.com> | 2013-10-13 13:35:10 -0300 |
---|---|---|
committer | ShadowNinja <shadowninja@minetest.net> | 2013-11-27 18:13:06 -0500 |
commit | 1fd9a11e302566d207c046bd5145e4e05a3c58ea (patch) | |
tree | dbd4d6a5daa3d5f29727e4087754c9066ff17b36 | |
parent | 4f246f0e2278aa677a74c631b25e760e8a197a3f (diff) | |
download | minetest-1fd9a11e302566d207c046bd5145e4e05a3c58ea.tar.gz minetest-1fd9a11e302566d207c046bd5145e4e05a3c58ea.tar.bz2 minetest-1fd9a11e302566d207c046bd5145e4e05a3c58ea.zip |
SQLite rollback
-rw-r--r-- | src/rollback.cpp | 1134 |
1 files changed, 960 insertions, 174 deletions
diff --git a/src/rollback.cpp b/src/rollback.cpp index e11006826..b95cc7bf3 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -29,11 +29,876 @@ with this program; if not, write to the Free Software Foundation, Inc., #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; + int 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<Entity> 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` INTEGER 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`) - ABS(?)) <= ?" + " AND (ABS(`y`) - ABS(?)) <= ?" + " AND (ABS(`z`) - ABS(?)) <= ?" + " ORDER BY `timestamp` DESC, `id` DESC" + " LIMIT 0,5" + , -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<const char *>(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<const char *>(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 [20 + (((bool) row.id)? 1: 0)], ii = 0; + bool nodeMeta = false; + + bind[ii++] = sqlite3_bind_int (dbs_do, 1, row.actor); + bind[ii++] = sqlite3_bind_int (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<ActionRow> actionRowsFromSelect (sqlite3_stmt* stmt) +{ + std::list<ActionRow> 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_int (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<const char*>(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<const char*>(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<const char*>(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<RollbackAction> rollbackActionsFromActionRows (std::list<ActionRow> rows) +{ + std::list<RollbackAction> actions; + std::list<ActionRow>::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<RollbackAction::Type>(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<ActionRow> SQL_getRowsSince (int firstTime, std::string actor = "") +{ + sqlite3_stmt * dbs_stmt = (!actor.length())? dbs_select: dbs_select_withActor; + sqlite3_reset (dbs_stmt); + sqlite3_bind_int (dbs_stmt, 1, firstTime); + + if (actor.length()) + sqlite3_bind_int (dbs_stmt, 2, getActorId(actor)); + + return actionRowsFromSelect(dbs_stmt); +} +std::list<ActionRow> SQL_getRowsSince_range (int firstTime, v3s16 p, int range) +{ + sqlite3_stmt * stmt = dbs_select_range; + sqlite3_reset(stmt); + + sqlite3_bind_int(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); + + return actionRowsFromSelect(stmt); +} +std::list<RollbackAction> SQL_getActionsSince_range (int firstTime, v3s16 p, int range) +{ + std::list<ActionRow> rows = SQL_getRowsSince_range(firstTime, p, range); + + return rollbackActionsFromActionRows(rows); +} +std::list<RollbackAction> SQL_getActionsSince (int firstTime, std::string actor = "") +{ + std::list<ActionRow> 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"); + + int filesize = fh.tellg(); + + if (filesize > 10) + { + fh.seekg(0); + + std::string bit; + int i = 0; + int id = 1; + int 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) << "%" + << "\tSpeed: " << i / t << " actions inserted per second " + << "\r"; + std::cout.flush(); + i = 0; + } + + ++id; + } + while (!fh.eof() && 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 static float getSuspectNearness(bool is_guess, v3s16 suspect_p, int suspect_t, @@ -56,38 +921,43 @@ static float getSuspectNearness(bool is_guess, v3s16 suspect_p, int suspect_t, 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)) + 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()){ + + if (action.actor.empty()) // If actor is not known, find out suspect or cancel + { v3s16 p; - if(!action.getPosition(&p)) + if (!action.getPosition(&p)) return; + action.actor = getSuspect(p, 83, 1); - if(action.actor.empty()) + 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; + + 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() @@ -140,213 +1010,131 @@ public: } void flush() { - infostream<<"RollbackManager::flush()"<<std::endl; - std::ofstream of(m_filepath.c_str(), std::ios::app); - if(!of.good()){ - errorstream<<"RollbackManager::flush(): Could not open file " - <<"for appending: \""<<m_filepath<<"\""<<std::endl; - return; - } - for(std::list<RollbackAction>::const_iterator - i = m_action_todisk_buffer.begin(); - i != m_action_todisk_buffer.end(); i++) + infostream << "RollbackManager::flush()" << std::endl; + + sqlite3_exec(dbh, "BEGIN", NULL, NULL, NULL); + + std::list<RollbackAction>::const_iterator iter; + + for (iter = m_action_todisk_buffer.begin(); + iter != m_action_todisk_buffer.end(); + iter++) { - // Do not save stuff that does not have an actor - if(i->actor == "") + if (iter->actor == "") continue; - of<<i->unix_time; - of<<" "; - of<<serializeJsonString(i->actor); - of<<" "; - of<<i->toString(); - if(i->actor_is_guess){ - of<<" "; - of<<"actor_is_guess"; - } - of<<std::endl; + + SQL_registerRow(actionRowFromRollbackAction(*iter)); } + + sqlite3_exec(dbh, "COMMIT", NULL, NULL, NULL); m_action_todisk_buffer.clear(); } - - // Other - 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; + 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(1+ filepath.rfind(DIR_DELIM)); + std::string filenameNew = (filenameOld == "rollback.txt")? "rollback.sqlite": filenameOld; + std::string filenameTXT = directory + "rollback.txt"; + std::string migratingFlag = filepath; + migratingFlag.append(".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; + 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() >= 100) + if(m_action_todisk_buffer.size() >= 500) flush(); } - - bool readFile(std::list<RollbackAction> &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: \""<<m_filepath<<"\""<<std::endl; - return false; - } - for(;;){ - if(f.eof() || !f.good()) - break; - std::string line; - std::getline(f, line); - line = trim(line); - if(line == "") - continue; - std::istringstream is(line); - - try{ - std::string action_time_raw; - std::getline(is, action_time_raw, ' '); - std::string action_actor; - try{ - action_actor = deSerializeJsonString(is); - }catch(SerializationError &e){ - errorstream<<"RollbackManager: Error deserializing actor: " - <<e.what()<<std::endl; - throw e; - } - RollbackAction action; - action.unix_time = stoi(action_time_raw); - action.actor = action_actor; - int c = is.get(); - if(c != ' '){ - is.putback(c); - throw SerializationError("readFile(): second ' ' not found"); - } - action.fromStream(is); - /*infostream<<"RollbackManager::readFile(): Action from disk: " - <<action.toString()<<std::endl;*/ - dst.push_back(action); - } - catch(SerializationError &e){ - errorstream<<"RollbackManager: Error on line: "<<line<<std::endl; - errorstream<<"RollbackManager: ^ error: "<<e.what()<<std::endl; - } - } - return true; - } - std::list<RollbackAction> getEntriesSince(int first_time) { - infostream<<"RollbackManager::getEntriesSince("<<first_time<<")"<<std::endl; - // Collect enough data to this buffer - std::list<RollbackAction> 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; - } - 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."<<std::endl; - action_buffer = m_action_latest_buffer; - } - } - return action_buffer; + infostream + << "RollbackManager::getEntriesSince(" << first_time << ")" + << std::endl; + + flush(); + + std::list<RollbackAction> result = SQL_getActionsSince(first_time); + + return result; } - - std::string getLastNodeActor(v3s16 p, int range, int seconds, - v3s16 *act_p, int *act_seconds) + std::string getLastNodeActor(v3s16 p, int range, int seconds, v3s16 *act_p, int *act_seconds) { - infostream<<"RollbackManager::getLastNodeActor("<<PP(p) - <<", "<<seconds<<")"<<std::endl; - // Figure out time int cur_time = time(0); int first_time = cur_time - seconds; - std::list<RollbackAction> action_buffer = getEntriesSince(first_time); - - std::list<RollbackAction> result; + std::list<RollbackAction> action_buffer = SQL_getActionsSince_range(first_time, p, range); + std::list<RollbackAction>::const_reverse_iterator iter; - for(std::list<RollbackAction>::const_reverse_iterator - i = action_buffer.rbegin(); - i != action_buffer.rend(); i++) + for (iter = action_buffer.rbegin(); + iter != action_buffer.rend(); + iter++) { - 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; - } + action_p.X = iter->p.X; + action_p.Y = iter->p.Y; + action_p.Z = iter->p.Z; - if(act_p) + if (act_p) *act_p = action_p; - if(act_seconds) - *act_seconds = cur_time - i->unix_time; - return i->actor; + + if (act_seconds) + *act_seconds = cur_time - iter->unix_time; + + return iter->actor; } + return ""; } - - std::list<RollbackAction> getRevertActions(const std::string &actor_filter, - int seconds) + std::list<RollbackAction> getRevertActions(const std::string &actor_filter, int seconds) { - infostream<<"RollbackManager::getRevertActions("<<actor_filter - <<", "<<seconds<<")"<<std::endl; + infostream + << "RollbackManager::getRevertActions(" << actor_filter + << ", " << seconds << ")" + << std::endl; + // Figure out time int cur_time = time(0); int first_time = cur_time - seconds; - std::list<RollbackAction> action_buffer = getEntriesSince(first_time); + flush(); - std::list<RollbackAction> result; - - for(std::list<RollbackAction>::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="<<action.unix_time - <<" actor=\""<<action.actor<<"\"" - <<" action="<<action.toString() - <<std::endl;*/ - result.push_back(action); - } + std::list<RollbackAction> result = SQL_getActionsSince(first_time, actor_filter); return result; } - private: std::string m_filepath; IGameDef *m_gamedef; @@ -360,5 +1148,3 @@ IRollbackManager *createRollbackManager(const std::string &filepath, IGameDef *g { return new RollbackManager(filepath, gamedef); } - - |