[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 "command_processor.h"
#include "db/database_manager.h"
#include "vfs.h"
#include "lua_api.h"
#include "lua_processor.h"
@ -9,7 +10,9 @@
#include "machine.h"
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),
_session_machine(home_machine),
_world_machines(world_machines) {

View File

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

View File

@ -13,19 +13,69 @@ void DatabaseManager::init(void) {
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"username TEXT NOT NULL UNIQUE,"
"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,
const std::string& hostname) {
long long player_id = 0;
long long machine_id = 0;
try {
_db << "BEGIN;";
/* Create the player. */
_db << "INSERT INTO players (username, password, hostname) VALUES (?, ?, ?);"
<< username
<< password
<< 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) {
/* Fail if the username exists. */
_db << "ROLLBACK;"; /* Ensure atomicity. */
return false;
}
return true;
@ -40,3 +90,11 @@ bool DatabaseManager::auth_player(const std::string& username, const std::string
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. */
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;
};

View File

@ -1,7 +1,9 @@
#include <filesystem>
#include <fstream>
#include <sstream>
#include <map>
#include "db/database_manager.h"
#include "machine_manager.h"
#include "machine.h"
#include "vfs.h"
@ -90,3 +92,53 @@ Machine* MachineManager::create_machine(uint32_t id, const std::string& hostname
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,
const std::string& system_type);
Machine* load_machine(long long machine_id);
private:
vfs_node* _vfs_template_root;
};

View File

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

View File

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

View File

@ -1,13 +1,14 @@
#include "player.h"
#include "command_processor.h"
#include "db/database_manager.h"
#include "machine.h"
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),
state(PlayerState::AUTHENTICATING) {
cmd_processor = new CommandProcessor(home_machine, world_machines);
cmd_processor = new CommandProcessor(home_machine, world_machines, db_manager);
}
Player::~Player(void) {

View File

@ -4,6 +4,7 @@
#include <map>
#include <string>
#include "db/database_manager.h"
#include "command_processor.h"
#include "machine.h"
@ -14,7 +15,8 @@ enum class PlayerState {
class Player {
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);
uint32_t id;