[Add] Completed the persistance layer.

This commit is contained in:
Ritchie Cunningham 2025-10-06 20:41:22 +01:00
parent fce3b3aad6
commit 44ca427c0e
10 changed files with 138 additions and 14 deletions

View File

@ -2,6 +2,7 @@
#include <sstream> #include <sstream>
#include "command_processor.h" #include "command_processor.h"
#include "db/database_manager.h"
#include "vfs.h" #include "vfs.h"
#include "lua_api.h" #include "lua_api.h"
#include "lua_processor.h" #include "lua_processor.h"
@ -9,7 +10,9 @@
#include "machine.h" #include "machine.h"
CommandProcessor::CommandProcessor(Machine* home_machine, CommandProcessor::CommandProcessor(Machine* home_machine,
std::map<std::string, Machine*>& world_machines) : std::map<std::string, Machine*>& world_machines,
DatabaseManager* db_manager) :
_db_manager(db_manager),
_home_machine(home_machine), _home_machine(home_machine),
_session_machine(home_machine), _session_machine(home_machine),
_world_machines(world_machines) { _world_machines(world_machines) {

View File

@ -4,12 +4,14 @@
#include <string> #include <string>
#include <map> #include <map>
#include "db/database_manager.h"
#include "machine.h" #include "machine.h"
#include "vfs.h" #include "vfs.h"
class CommandProcessor { class CommandProcessor {
public: public:
CommandProcessor(Machine* home_machine, std::map<std::string, Machine*>& world_machines); CommandProcessor(Machine* home_machine, std::map<std::string, Machine*>& world_machines,
DatabaseManager* db_manager);
~CommandProcessor(void); ~CommandProcessor(void);
std::string process_command(const std::string& command); std::string process_command(const std::string& command);
@ -28,6 +30,7 @@ public:
void ensure_vfs_is_writable(void); void ensure_vfs_is_writable(void);
private: private:
DatabaseManager* _db_manager;
Machine* _home_machine; Machine* _home_machine;
Machine* _session_machine; Machine* _session_machine;
vfs_node* _current_dir; vfs_node* _current_dir;

View File

@ -13,19 +13,69 @@ void DatabaseManager::init(void) {
"id INTEGER PRIMARY KEY AUTOINCREMENT," "id INTEGER PRIMARY KEY AUTOINCREMENT,"
"username TEXT NOT NULL UNIQUE," "username TEXT NOT NULL UNIQUE,"
"password TEXT NOT NULL," "password TEXT NOT NULL,"
"hostname TEXT NOT NULL" "hostname TEXT NOT NULL,"
"home_machine_id INTEGER"
");";
_db << "CREATE TABLE IF NOT EXISTS machines ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"owner_id INTEGER,"
"hostname TEXT NOT NULL,"
"ip_address TEXT NOT NULL UNIQUE"
");";
_db << "CREATE TABLE IF NOT EXISTS vfs_nodes ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"machine_id INTEGER NOT NULL,"
"parent_id INTEGER,"
"name TEXT NOT NULL,"
"type INTEGER NOT NULL,"
"content TEXT"
");"; ");";
} }
bool DatabaseManager::create_player(const std::string& username, const std::string& password, bool DatabaseManager::create_player(const std::string& username, const std::string& password,
const std::string& hostname) { const std::string& hostname) {
long long player_id = 0;
long long machine_id = 0;
try { try {
_db << "BEGIN;";
/* Create the player. */
_db << "INSERT INTO players (username, password, hostname) VALUES (?, ?, ?);" _db << "INSERT INTO players (username, password, hostname) VALUES (?, ?, ?);"
<< username << username
<< password << password
<< hostname; << hostname;
player_id = _db.last_insert_rowid();
/* Create the home machine. */
/* TODO: Implement real IP allication. */
std::string ip_address = "192.168.1." + std::to_string(player_id);
_db << "INSERT INTO machines (owner_id, hostname, ip_address) VALUES (?, ?, ?);"
<< player_id << hostname << ip_address;
machine_id = _db.last_insert_rowid();
/* Link player to their new machine. */
_db << "UPDATE players SET home_machine_id = ? WHERE id = ?;"
<< machine_id << player_id;
/* Create the root dir for the new machine's VFS. */
_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type) VALUES(?, NULL, ?, 1);"
<< machine_id << "/";
long long root_id = _db.last_insert_rowid();
/* Create default subdirs. */
_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type) VALUES (?, ?, ?, 1);"
<< machine_id << root_id << "bin";
_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type) VALUES (?, ?, ?, 1);"
<< machine_id << root_id << "home";
_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type) VALUES (?, ?, ?, 1);"
<< machine_id << root_id << "etc";
_db << "COMMIT";
} catch(const std::exception& e) { } catch(const std::exception& e) {
/* Fail if the username exists. */ _db << "ROLLBACK;"; /* Ensure atomicity. */
return false; return false;
} }
return true; return true;
@ -40,3 +90,11 @@ bool DatabaseManager::auth_player(const std::string& username, const std::string
return authed; return authed;
} }
long long DatabaseManager::get_player_home_machine_id(const std::string& username) {
long long machine_id = -1; /* Return -1 if not found. */
_db << "SELECT home_machine_id FROM players WHERE username = ?;"
<< username
>> machine_id;
return machine_id;
}

View File

@ -18,6 +18,7 @@ public:
/* Return true if creds are valid. */ /* Return true if creds are valid. */
bool auth_player(const std::string& username, const std::string& password); bool auth_player(const std::string& username, const std::string& password);
private: long long get_player_home_machine_id(const std::string& username);
sqlite::database _db; sqlite::database _db;
}; };

View File

@ -1,7 +1,9 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <map>
#include "db/database_manager.h"
#include "machine_manager.h" #include "machine_manager.h"
#include "machine.h" #include "machine.h"
#include "vfs.h" #include "vfs.h"
@ -90,3 +92,53 @@ Machine* MachineManager::create_machine(uint32_t id, const std::string& hostname
return new_machine; return new_machine;
} }
/* Recursively build the VFS tree from database nodes. */
void build_tree(vfs_node* parent, const std::map<long long, vfs_node*>& nodes) {
for(auto const& [id, node] : nodes) {
/* Inefficient but safe. Would be better to group nodes by parent_id. */
if(node->parent_id == parent->id) {
parent->children[node->name] = node;
node->parent = parent;
if(node->type == DIR_NODE) {
build_tree(node, nodes);
}
}
}
}
Machine* MachineManager::load_machine(long long machine_id) {
DatabaseManager db("bettola.db"); /* Assumes multiplayer for now. */
std::string hostname;
db._db << "SELECT hostname FROM machines WHERE id = ?;"
<< machine_id
>> hostname;
Machine* machine = new Machine(machine_id, hostname);
/* Load all VFS nodes for this machine from the database. */
std::map<long long, vfs_node*> nodes;
vfs_node* root = nullptr;
db._db << "SELECT id, parent_id, name, type, content FROM vfs_nodes WHERE machine_id = ?;"
<< machine_id
>> [&](long long id, long long parent_id, std::string name, int type, std::string content) {
vfs_node* node = new_node(name, (vfs_node_type)type, nullptr);
node->id = id;
node->parent_id = parent_id; /* Store temp id for tree building. */
node->content = content;
nodes[id] = node;
if(name == "/") {
root = node;
}
};
if(root) {
build_tree(root, nodes);
machine->vfs_root = root;
}
return machine;
}

View File

@ -15,6 +15,7 @@ public:
Machine* create_machine(uint32_t id, const std::string& hostname, Machine* create_machine(uint32_t id, const std::string& hostname,
const std::string& system_type); const std::string& system_type);
Machine* load_machine(long long machine_id);
private: private:
vfs_node* _vfs_template_root; vfs_node* _vfs_template_root;
}; };

View File

@ -14,6 +14,8 @@ enum vfs_node_type {
}; };
struct vfs_node { struct vfs_node {
long long id;
long long parent_id; /* Used durin DB loading only. */
std::string name; std::string name;
vfs_node_type type; vfs_node_type type;

View File

@ -2,7 +2,6 @@
#include <exception> #include <exception>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <sstream>
#include "network_manager.h" #include "network_manager.h"
#include "db/database_manager.h" #include "db/database_manager.h"
@ -78,7 +77,8 @@ void NetworkManager::start_accept(void) {
uint32_t player_id = _next_player_id++; uint32_t player_id = _next_player_id++;
Machine* player_machine = _machine_manager.create_machine(player_id, "player.home", Machine* player_machine = _machine_manager.create_machine(player_id, "player.home",
"player"); "player");
auto new_player = std::make_unique<Player>(player_id, player_machine, _world_machines); auto new_player = std::make_unique<Player>(player_id, player_machine,
_world_machines, _db_manager.get());
Player* new_player_ptr = new_player.get(); Player* new_player_ptr = new_player.get();
_players[player_id] = std::move(new_player); _players[player_id] = std::move(new_player);
new_connection->set_id(player_id); new_connection->set_id(player_id);
@ -130,10 +130,11 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
auto parts = split_message(payload, "::"); auto parts = split_message(payload, "::");
if(parts.size() == 3) { if(parts.size() == 3) {
if(_db_manager->create_player(parts[0], parts[1], parts[2])) { if(_db_manager->create_player(parts[0], parts[1], parts[2])) {
/* long long machine_id = _db_manager->get_player_home_machine_id(parts[0]);
* TODO: When creating a player, also create their machine and save it. Machine* home_machine = _machine_manager.load_machine(machine_id);
* for now, they will juse use a temp machine on auth. delete player->cmd_processor; /* Delete old command processor. */
*/ player->cmd_processor = new CommandProcessor(home_machine, _world_machines,
_db_manager.get());
player->state = PlayerState::ACTIVE; player->state = PlayerState::ACTIVE;
connection->send("C_ACC_SUCCESS"); connection->send("C_ACC_SUCCESS");
/* send initial prompt. */ /* send initial prompt. */

View File

@ -1,13 +1,14 @@
#include "player.h" #include "player.h"
#include "command_processor.h" #include "command_processor.h"
#include "db/database_manager.h"
#include "machine.h" #include "machine.h"
Player::Player(uint32_t new_id, Machine* home_machine, Player::Player(uint32_t new_id, Machine* home_machine,
std::map<std::string, Machine*>& world_machines) : std::map<std::string, Machine*>& world_machines, DatabaseManager* db_manager) :
id(new_id), id(new_id),
state(PlayerState::AUTHENTICATING) { state(PlayerState::AUTHENTICATING) {
cmd_processor = new CommandProcessor(home_machine, world_machines); cmd_processor = new CommandProcessor(home_machine, world_machines, db_manager);
} }
Player::~Player(void) { Player::~Player(void) {

View File

@ -4,6 +4,7 @@
#include <map> #include <map>
#include <string> #include <string>
#include "db/database_manager.h"
#include "command_processor.h" #include "command_processor.h"
#include "machine.h" #include "machine.h"
@ -14,7 +15,8 @@ enum class PlayerState {
class Player { class Player {
public: public:
Player(uint32_t id, Machine* home_machine, std::map<std::string, Machine*>& world_machines); Player(uint32_t id, Machine* home_machine, std::map<std::string,
Machine*>& world_machines, DatabaseManager* db_manager);
~Player(void); ~Player(void);
uint32_t id; uint32_t id;