[Add] Implement remote sessions and server exit.

This commit is contained in:
Ritchie Cunningham 2025-09-26 21:32:08 +01:00
parent c7e8d97c63
commit bb17cf3473
12 changed files with 94 additions and 48 deletions

View File

@ -0,0 +1,7 @@
-- /bin/cd - Change Directory.
local target = arg[1]
if not target then
return "" -- No argument, just return to prompt.
end
return { action = "cd", target = target }

View File

@ -0,0 +1,6 @@
-- /bin/exit - Disconnects from a remote session or cloes terminal window.
if is_remote_session then
return { action = "disconnect" }
else
return { action = "close_terminal" }
end

View File

@ -52,6 +52,17 @@ bool ClientNetwork::connect(const std::string& host, uint16_t port) {
} }
void ClientNetwork::disconnect(void) { void ClientNetwork::disconnect(void) {
/* Stop the context, should cause the io_context.run() call in the
* background thread to return.
*/
_io_context.stop();
/* Wait for the background thread to finish. */
if(_context_thread.joinable()) {
_context_thread.join();
}
/* Should be safe to close the socket no that thread is stoped <questionmark> */
if(is_connected()) { if(is_connected()) {
/* Close the socket. Causes outstanding async operations /* Close the socket. Causes outstanding async operations
* in TcpConnection to compete with an error, which in return * in TcpConnection to compete with an error, which in return

View File

@ -32,6 +32,11 @@ Terminal::~Terminal(void) {}
void Terminal::update(void) { void Terminal::update(void) {
std::string server_msg; std::string server_msg;
while(_network->poll_message(server_msg)) { while(_network->poll_message(server_msg)) {
if(server_msg == "__CLOSE_CONNECTION__") {
_history.push_back("Connection closed by server.");
_should_close = true;
return;
}
/* Server will send "output\nprompt". Split them. */ /* Server will send "output\nprompt". Split them. */
size_t last_newline = server_msg.find_last_of('\n'); size_t last_newline = server_msg.find_last_of('\n');
if(last_newline != std::string::npos) { if(last_newline != std::string::npos) {
@ -59,13 +64,7 @@ void Terminal::_on_ret_press(void) {
/* Add the command to history. */ /* Add the command to history. */
_history.push_back(_prompt + "> " + command); _history.push_back(_prompt + "> " + command);
if(command == "exit") { if(command == "clear") {
_should_close = true;
/* TODO: Window will close, and the OS will close the socket.
* server will detect the disconnection.
*/
return;
} else if(command == "clear"){
_history.clear(); _history.clear();
return; return;
} }

View File

@ -1,15 +1,17 @@
#include "command_processor.h" #include "command_processor.h"
#include <optional>
#include <sstream> #include <sstream>
#include "vfs.h" #include "vfs.h"
#include "lua_processor.h" #include "lua_processor.h"
#include "vfs_manager.h" #include "vfs_manager.h"
CommandProcessor::CommandProcessor(vfs_node* starting_dir, CommandProcessor::CommandProcessor(vfs_node* home_vfs,
std::map<std::string, vfs_node*>& world_vfs) : std::map<std::string, vfs_node*>& world_vfs) :
_world_vfs(world_vfs) { _world_vfs(world_vfs) {
_current_dir = starting_dir;
_lua = new LuaProcessor(); _lua = new LuaProcessor();
home_vfs_root = home_vfs;
session_vfs_root = home_vfs_root;
_current_dir = session_vfs_root;
} }
CommandProcessor::~CommandProcessor(void) { CommandProcessor::~CommandProcessor(void) {
@ -46,7 +48,8 @@ std::string CommandProcessor::process_command(const std::string& command) {
} }
if(root->children.count("bin") && root->children["bin"]->children.count(script_filename)) { if(root->children.count("bin") && root->children["bin"]->children.count(script_filename)) {
vfs_node* script_node = root->children["bin"]->children[script_filename]; vfs_node* script_node = root->children["bin"]->children[script_filename];
sol::object result = _lua->execute(script_node->content, _current_dir, args); bool is_remote = (session_vfs_root != home_vfs_root);
sol::object result = _lua->execute(script_node->content, _current_dir, args, is_remote);
if(result.is<std::string>()) { if(result.is<std::string>()) {
return result.as<std::string>(); return result.as<std::string>();
} else if(result.is<sol::table>()) { } else if(result.is<sol::table>()) {
@ -54,29 +57,7 @@ std::string CommandProcessor::process_command(const std::string& command) {
} }
return "[Script returned an unexpected type]"; return "[Script returned an unexpected type]";
} }
return "Unknown command: " + command_name + "\n";
/* === Tmp fallback for built-in C++ commands. */
if(command.rfind("cd ", 0) == 0) {
std::string target_dir_name = command.substr(3);
if(target_dir_name == "..") {
if(_current_dir->parent) {
_current_dir = _current_dir->parent;
}
} else if(_current_dir->children.count(target_dir_name)) {
vfs_node* target_node = _current_dir->children[target_dir_name];
if(target_node->type == DIR_NODE) {
_current_dir = target_node;
} else {
return "cd: not a directory\n";
}
} else {
return "cd: no such file or directory\n";
}
return get_full_path(_current_dir);
}
return "Unknown command: " + command + "\n";
} }
std::string CommandProcessor::_handle_vfs_action(sol::table action) { std::string CommandProcessor::_handle_vfs_action(sol::table action) {
@ -130,10 +111,34 @@ std::string CommandProcessor::_handle_vfs_action(sol::table action) {
} else if(action_name == "ssh") { } else if(action_name == "ssh") {
std::string target_ip = action["target"].get_or<std::string>(""); std::string target_ip = action["target"].get_or<std::string>("");
if(_world_vfs.count(target_ip)) { if(_world_vfs.count(target_ip)) {
_current_dir = _world_vfs[target_ip]; session_vfs_root = _world_vfs[target_ip];
_current_dir = session_vfs_root;
return "Connected to " + target_ip; return "Connected to " + target_ip;
} }
return "ssh: Could not resolve hostname " + target_ip + ": Name or service not known"; return "ssh: Could not resolve hostname " + target_ip + ": Name or service not known";
} else if(action_name == "cd") {
std::string target_dir_name = action["target"].get_or<std::string>("");
if(target_dir_name == "..") {
if(_current_dir->parent) {
_current_dir = _current_dir->parent;
}
} else if(_current_dir->children.count(target_dir_name)) {
vfs_node* target_node = _current_dir->children[target_dir_name];
if(target_node->type == DIR_NODE) {
_current_dir = target_node;
} else {
return std::string("cd: not a directory: ") + target_dir_name;
}
} else {
return std::string("cd: no such file or directory: ") + target_dir_name;
}
return ""; /* Success. */
} else if(action_name == "disconnect") {
session_vfs_root = home_vfs_root;
_current_dir = home_vfs_root;
return "Connection closed.";
} else if(action_name == "close_terminal") {
return "__CLOSE_CONNECTION__";
} }
return "Error: Unknown VFS action '" + action_name + "'"; return "Error: Unknown VFS action '" + action_name + "'";
} }

View File

@ -1,14 +1,14 @@
#pragma once #pragma once
#include <lua_processor.h>
#include <string> #include <string>
#include <map> #include <map>
#include "vfs.h" #include "vfs.h"
#include <lua_processor.h>
class CommandProcessor { class CommandProcessor {
public: public:
CommandProcessor(vfs_node* starting_dir, std::map<std::string, vfs_node*>& world_vfs); CommandProcessor(vfs_node* home_vfs, std::map<std::string, vfs_node*>& world_vfs);
~CommandProcessor(void); ~CommandProcessor(void);
std::string process_command(const std::string& command); std::string process_command(const std::string& command);
@ -16,6 +16,8 @@ public:
private: private:
std::string _handle_vfs_action(sol::table action); std::string _handle_vfs_action(sol::table action);
vfs_node* home_vfs_root;
vfs_node* session_vfs_root;
vfs_node* _current_dir; vfs_node* _current_dir;
std::map<std::string, vfs_node*>& _world_vfs; std::map<std::string, vfs_node*>& _world_vfs;
LuaProcessor* _lua; LuaProcessor* _lua;

View File

@ -17,9 +17,10 @@ LuaProcessor::LuaProcessor(void) {
LuaProcessor::~LuaProcessor(void) {} LuaProcessor::~LuaProcessor(void) {}
sol::object LuaProcessor::execute(const std::string& script, vfs_node* current_dir, sol::object LuaProcessor::execute(const std::string& script, vfs_node* current_dir,
const std::vector<std::string>& args) { const std::vector<std::string>& args, bool is_remote) {
try { try {
/* Pass C++ objects/points into the Lua env. */ /* Pass C++ objects/points into the Lua env. */
_lua["is_remote_session"] = is_remote;
_lua["current_dir"] = current_dir; _lua["current_dir"] = current_dir;
/* Create and populate the 'arg' table for the script. */ /* Create and populate the 'arg' table for the script. */

View File

@ -12,7 +12,7 @@ public:
/* Executes a string of lua code and returns result as a string. */ /* Executes a string of lua code and returns result as a string. */
sol::object execute(const std::string& script, vfs_node* current_dir, sol::object execute(const std::string& script, vfs_node* current_dir,
const std::vector<std::string>& args); const std::vector<std::string>& args, bool is_remote);
private: private:
sol::state _lua; sol::state _lua;
}; };

View File

@ -78,7 +78,9 @@ void TcpConnection::async_read_header(void) {
/* Wait for next message. */ /* Wait for next message. */
async_read_header(); async_read_header();
} else { } else {
if(_socket.is_open()) {
_socket.close(); _socket.close();
}
if(_on_disconnect) _on_disconnect(); if(_on_disconnect) _on_disconnect();
} }
}); });
@ -92,7 +94,9 @@ void TcpConnection::async_read_header(void) {
/* Peer disconnected cleanly? */ /* Peer disconnected cleanly? */
if(ec != asio::error::eof) { if(ec != asio::error::eof) {
} }
if(_socket.is_open()) {
_socket.close(); _socket.close();
}
if(_on_disconnect) _on_disconnect(); if(_on_disconnect) _on_disconnect();
} }
}); });
@ -110,7 +114,9 @@ void TcpConnection::async_write_header() {
async_write_header(); async_write_header();
} }
} else { } else {
if(_socket.is_open()) {
_socket.close(); _socket.close();
}
if(_on_disconnect) _on_disconnect(); if(_on_disconnect) _on_disconnect();
} }
}); });

View File

@ -97,6 +97,13 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
fprintf(stderr, "[Player %u] Command: '%s'\n", player->id, message.c_str()); fprintf(stderr, "[Player %u] Command: '%s'\n", player->id, message.c_str());
std::string response = player->cmd_processor->process_command(message); std::string response = player->cmd_processor->process_command(message);
if(response == "__CLOSE_CONNECTION__") {
connection->send(response);
/* Just let me close the f.cking terminal? */
return;
}
std::string new_prompt = get_full_path(player->cmd_processor->get_current_dir()); std::string new_prompt = get_full_path(player->cmd_processor->get_current_dir());
response += "\n" + new_prompt; response += "\n" + new_prompt;

View File

@ -3,14 +3,15 @@
Player::Player(uint32_t new_id, VFSManager& vfs_manager, Player::Player(uint32_t new_id, VFSManager& vfs_manager,
std::map<std::string, vfs_node*>& world_vfs) : std::map<std::string, vfs_node*>& world_vfs) :
id(new_id), id(new_id) {
vfs_root(vfs_manager.create_vfs("player")), vfs_node* player_vfs = vfs_manager.create_vfs("player");
cmd_processor(new CommandProcessor(vfs_root, world_vfs)) {} cmd_processor = new CommandProcessor(player_vfs, world_vfs);
}
Player::~Player(void) { Player::~Player(void) {
if(cmd_processor) { if(cmd_processor) {
delete cmd_processor; delete cmd_processor;
} }
/* TODO: The VFSManager should handle deleting the vfs_root. */ /* TODO: The VFSManager should handle deleting the player_vfs_root. */
} }

View File

@ -2,9 +2,11 @@
#include <cstdint> #include <cstdint>
#include "command_processor.h"
#include "vfs.h" #include "vfs.h"
#include "vfs_manager.h" #include "vfs_manager.h"
#include "command_processor.h"
class CommandProcessor;
class Player { class Player {
public: public:
@ -12,6 +14,5 @@ public:
~Player(void); ~Player(void);
uint32_t id; uint32_t id;
vfs_node* vfs_root;
CommandProcessor* cmd_processor; /* Manages the VFS state for the remote session. */ CommandProcessor* cmd_processor; /* Manages the VFS state for the remote session. */
}; };