From 28382b249535e964ce983fe5a0821a898a077775 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 28 Feb 2026 17:35:40 +0100 Subject: multi file for both sides --- src/bin/client.rs | 98 --------------------------------------- src/bin/client/connection.rs | 32 +++++++++++++ src/bin/client/database.rs | 30 ++++++++++++ src/bin/client/main.rs | 17 +++++++ src/bin/client/mod.rs | 4 ++ src/bin/client/platform.rs | 28 +++++++++++ src/bin/client/repl.rs | 108 +++++++++++++++++++++++++++++++++++++++++++ src/bin/server.rs | 66 -------------------------- src/bin/server/args.rs | 41 ++++++++++++++++ src/bin/server/handler.rs | 15 ++++++ src/bin/server/main.rs | 23 +++++++++ src/bin/server/mod.rs | 2 + 12 files changed, 300 insertions(+), 164 deletions(-) delete mode 100644 src/bin/client.rs create mode 100644 src/bin/client/connection.rs create mode 100644 src/bin/client/database.rs create mode 100644 src/bin/client/main.rs create mode 100644 src/bin/client/mod.rs create mode 100644 src/bin/client/platform.rs create mode 100644 src/bin/client/repl.rs delete mode 100644 src/bin/server.rs create mode 100644 src/bin/server/args.rs create mode 100644 src/bin/server/handler.rs create mode 100644 src/bin/server/main.rs create mode 100644 src/bin/server/mod.rs (limited to 'src') diff --git a/src/bin/client.rs b/src/bin/client.rs deleted file mode 100644 index 45c5090..0000000 --- a/src/bin/client.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::io::{BufRead, BufReader, Write, stdin, stdout}; -use std::net::TcpStream; -use std::time::Instant; - -#[cfg(windows)] -mod win { - use std::io; - use std::os::windows::io::AsRawHandle; - use winapi::um::consoleapi::GetConsoleMode; - use winapi::um::consoleapi::SetConsoleMode; - use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; - use winapi::um::winbase::STD_OUTPUT_HANDLE; - use winapi::um::processenv::GetStdHandle; - use winapi::shared::minwindef::DWORD; - - pub fn enable_vt() -> io::Result<()> { - unsafe { - let h = GetStdHandle(STD_OUTPUT_HANDLE); - if h.is_null() { return Err(io::Error::last_os_error()); } - - let mut mode: DWORD = 0; - if GetConsoleMode(h, &mut mode) == 0 { - return Err(io::Error::last_os_error()); - } - let new = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if SetConsoleMode(h, new) == 0 { - return Err(io::Error::last_os_error()); - } - Ok(()) - } - } -} - -fn main() { - #[cfg(windows)] - win::enable_vt().unwrap(); - - let mut addr: Option; - let mut stream: Option = None; - loop { - print!("Command: "); - stdout().flush().unwrap(); - let mut line = String::new(); - stdin().read_line(&mut line).unwrap(); - let line = line.trim(); - if line.is_empty() { - continue; - } - let parts: Vec<&str> = line.splitn(2, ' ').collect(); - match parts[0] { - "connect" => { - if parts.len() < 2 { - println!("Usage: connect "); - continue; - } - addr = Some(parts[1].to_string()); - match TcpStream::connect(addr.as_ref().unwrap()) { - Ok(s) => { - let mut ping_writer = s.try_clone().unwrap(); - let mut ping_reader = BufReader::new(s.try_clone().unwrap()); - let start = Instant::now(); - writeln!(ping_writer, "PING").unwrap(); - let mut response = String::new(); - ping_reader.read_line(&mut response).unwrap(); - let elapsed = start.elapsed(); - println!("Connected to {} with ping delay: {:?}", addr.as_ref().unwrap(), elapsed); - stream = Some(s); - } - Err(e) => { - println!("Failed to connect: {}", e); - } - } - } - "send" => { - if stream.is_none() { - println!("Not connected. Use 'connect ' first."); - continue; - } - if parts.len() < 2 { - println!("Usage: send "); - continue; - } - let msg = parts[1]; - let s = stream.as_mut().unwrap(); - let writer_stream = s.try_clone().unwrap(); - let mut writer = writer_stream; - let response_reader_stream = s.try_clone().unwrap(); - let mut response_reader = BufReader::new(response_reader_stream); - writeln!(writer, "{}", msg).unwrap(); - let mut response = String::new(); - response_reader.read_line(&mut response).unwrap(); - println!("Echo: {}", response.trim()); - } - "quit" => break, - _ => println!("Unknown command. Available: connect, send, quit"), - } - } -} diff --git a/src/bin/client/connection.rs b/src/bin/client/connection.rs new file mode 100644 index 0000000..9e402cb --- /dev/null +++ b/src/bin/client/connection.rs @@ -0,0 +1,32 @@ +use std::io::{BufRead, BufReader, Write}; +use std::net::TcpStream; +use std::sync::mpsc; +use std::thread; +use std::time::{Duration, Instant}; + +pub fn connect(addr: &str) -> Result> { + let (tx, rx) = mpsc::channel(); + let addr_str = addr.to_string(); + thread::spawn(move || { + match TcpStream::connect(&addr_str) { + Ok(s) => tx.send(Ok(s)).unwrap(), + Err(e) => tx.send(Err(e)).unwrap(), + } + }); + match rx.recv_timeout(Duration::from_secs(3)) { + Ok(Ok(s)) => { + let mut ping_writer = s.try_clone().unwrap(); + let mut ping_reader = BufReader::new(s.try_clone().unwrap()); + let start = Instant::now(); + writeln!(ping_writer, "PING").unwrap(); + let mut response = String::new(); + ping_reader.read_line(&mut response).unwrap(); + let elapsed = start.elapsed(); + println!("Connected to {} with ping delay: {:?}", addr, elapsed); + Ok(s) + } + Ok(Err(e)) => Err(Box::new(e)), + Err(mpsc::RecvTimeoutError::Timeout) => Err("Connection timed out after 3 seconds".into()), + Err(_) => Err("Connection attempt failed".into()), + } +} diff --git a/src/bin/client/database.rs b/src/bin/client/database.rs new file mode 100644 index 0000000..bc8f6f8 --- /dev/null +++ b/src/bin/client/database.rs @@ -0,0 +1,30 @@ +use rusqlite::Connection; + +pub fn init_db() -> Connection { + let conn = Connection::open("client.db").unwrap(); + conn.execute( + "CREATE TABLE IF NOT EXISTS hosts ( + host TEXT PRIMARY KEY, + last_used DATETIME DEFAULT CURRENT_TIMESTAMP + )", + [], + ).unwrap(); + conn +} + +pub fn get_last_host(conn: &Connection) -> Option { + let mut stmt = conn.prepare("SELECT host FROM hosts ORDER BY last_used DESC LIMIT 1").unwrap(); + let mut rows = stmt.query([]).unwrap(); + if let Ok(Some(row)) = rows.next() { + row.get(0).ok() + } else { + None + } +} + +pub fn save_host(conn: &Connection, host: &str) { + conn.execute( + "INSERT OR REPLACE INTO hosts (host, last_used) VALUES (?1, CURRENT_TIMESTAMP)", + [host], + ).unwrap(); +} diff --git a/src/bin/client/main.rs b/src/bin/client/main.rs new file mode 100644 index 0000000..ce9c92f --- /dev/null +++ b/src/bin/client/main.rs @@ -0,0 +1,17 @@ +mod repl; +mod database; +mod connection; +mod platform; + +use repl::ClientState; + +fn main() { + #[cfg(windows)] + platform::win::enable_vt().unwrap(); + + let mut state = ClientState::new(); + if let Some(host) = state.addr.clone() { + state.connect_to(&host); + } + state.run(); +} diff --git a/src/bin/client/mod.rs b/src/bin/client/mod.rs new file mode 100644 index 0000000..938d862 --- /dev/null +++ b/src/bin/client/mod.rs @@ -0,0 +1,4 @@ +pub mod database; +pub mod connection; +pub mod platform; +pub mod repl; diff --git a/src/bin/client/platform.rs b/src/bin/client/platform.rs new file mode 100644 index 0000000..1a6d71b --- /dev/null +++ b/src/bin/client/platform.rs @@ -0,0 +1,28 @@ +#[cfg(windows)] +pub mod win { + use std::io; + use std::os::windows::io::AsRawHandle; + use winapi::um::consoleapi::GetConsoleMode; + use winapi::um::consoleapi::SetConsoleMode; + use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; + use winapi::um::winbase::STD_OUTPUT_HANDLE; + use winapi::um::processenv::GetStdHandle; + use winapi::shared::minwindef::DWORD; + + pub fn enable_vt() -> io::Result<()> { + unsafe { + let h = GetStdHandle(STD_OUTPUT_HANDLE); + if h.is_null() { return Err(io::Error::last_os_error()); } + + let mut mode: DWORD = 0; + if GetConsoleMode(h, &mut mode) == 0 { + return Err(io::Error::last_os_error()); + } + let new = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if SetConsoleMode(h, new) == 0 { + return Err(io::Error::last_os_error()); + } + Ok(()) + } + } +} diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs new file mode 100644 index 0000000..d38c54d --- /dev/null +++ b/src/bin/client/repl.rs @@ -0,0 +1,108 @@ +use std::io::{BufRead, BufReader, Write, stdin, stdout}; +use std::net::TcpStream; +use rusqlite::Connection; + +use crate::connection; +use crate::database; + +pub struct ClientState { + pub stream: Option, + pub conn: Connection, + pub addr: Option, + pub default_port: u16, +} + +impl ClientState { + pub fn new() -> Self { + let conn = database::init_db(); + let default_ip = "127.0.0.1".to_string(); + let default_port = 11525; + let last_host = database::get_last_host(&conn); + let default_host = format!("{}:{}", default_ip, default_port); + let addr = last_host.or(Some(default_host)); + + Self { + stream: None, + conn, + addr, + default_port, + } + } + + pub fn connect_to(&mut self, addr: &str) { + self.addr = Some(addr.to_string()); + match connection::connect(addr) { + Ok(s) => { + self.stream = Some(s); + database::save_host(&self.conn, addr); + } + Err(e) => println!("Failed to connect: {}", e), + } + } + + pub fn run(&mut self) { + loop { + print!("> "); + stdout().flush().unwrap(); + let mut line = String::new(); + stdin().read_line(&mut line).unwrap(); + let line = line.trim(); + if line.is_empty() { + continue; + } + let parts: Vec<&str> = line.splitn(2, ' ').collect(); + match parts[0] { + "connect" => { + let addr_str = if parts.len() < 2 { + if let Some(h) = &self.addr { + h.clone() + } else { + println!("No host specified and no previous connection. Usage: connect :"); + continue; + } + } else { + let mut a = parts[1].to_string(); + if !a.contains(':') { + a = format!("{}:{}", a, self.default_port); + } + a + }; + self.connect_to(&addr_str); + } + "send" => { + if self.stream.is_none() { + println!("Not connected. Use 'connect :' first."); + continue; + } + if parts.len() < 2 { + println!("Usage: send "); + continue; + } + let msg = parts[1]; + let s = self.stream.as_mut().unwrap(); + let writer_stream = s.try_clone().unwrap(); + let mut writer = writer_stream; + let response_reader_stream = s.try_clone().unwrap(); + let mut response_reader = BufReader::new(response_reader_stream); + writeln!(writer, "{}", msg).unwrap(); + let mut response = String::new(); + response_reader.read_line(&mut response).unwrap(); + println!("Echo: {}", response.trim()); + } + "disconnect" => { + self.stream = None; + println!("Disconnected"); + } + "help" => { + help(); + } + "quit" => break, + _ => help(), + } + } + } +} + +fn help() { + println!("Available commands: connect :, send , disconnect, help, quit"); +} diff --git a/src/bin/server.rs b/src/bin/server.rs deleted file mode 100644 index 215d2ae..0000000 --- a/src/bin/server.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::io::{BufRead, BufReader, Write}; -use std::net::TcpListener; -use std::thread; - -fn handle_client(stream: std::net::TcpStream) { - println!("Client connected"); - let reader_stream = stream.try_clone().unwrap(); - let reader = BufReader::new(reader_stream); - let mut writer = stream; - for line in reader.lines() { - let line = line.unwrap(); - println!("Received: {}", line); - writeln!(writer, "{}", line).unwrap(); - } - println!("Client disconnected"); -} - -fn main() { - let args: Vec = std::env::args().collect(); - let mut ip = "127.0.0.1".to_string(); - let mut port = 8080; - let mut i = 1; - while i < args.len() { - if args[i] == "-p" { - if i + 1 < args.len() { - port = args[i + 1].parse::().unwrap_or_else(|_| { - eprintln!("Invalid port number: {}", args[i + 1]); - std::process::exit(1); - }); - i += 2; - } else { - eprintln!("-p requires a port number"); - std::process::exit(1); - } - } else if args[i] == "-i" { - if i + 1 < args.len() { - ip = args[i + 1].clone(); - if ip != "127.0.0.1" && ip != "0.0.0.0" { - eprintln!("Invalid IP address: {}. Must be 127.0.0.1 or 0.0.0.0", ip); - std::process::exit(1); - } - i += 2; - } else { - eprintln!("-i requires an IP address"); - std::process::exit(1); - } - } else { - eprintln!("Unknown argument: {}", args[i]); - std::process::exit(1); - } - } - let listener = match TcpListener::bind(format!("{}:{}", ip, port)) { - Ok(l) => l, - Err(e) => { - eprintln!("Failed to bind to {}:{}: {}", ip, port, e); - std::process::exit(1); - } - }; - println!("Server listening on {}", listener.local_addr().unwrap()); - for stream in listener.incoming() { - let stream = stream.unwrap(); - thread::spawn(move || { - handle_client(stream); - }); - } -} diff --git a/src/bin/server/args.rs b/src/bin/server/args.rs new file mode 100644 index 0000000..31c0acc --- /dev/null +++ b/src/bin/server/args.rs @@ -0,0 +1,41 @@ +pub struct Args { + pub ip: String, + pub port: u16, +} + +pub fn parse_args() -> Args { + let args: Vec = std::env::args().collect(); + let mut ip = "127.0.0.1".to_string(); + let mut port = 11525; + let mut i = 1; + while i < args.len() { + if args[i] == "-p" { + if i + 1 < args.len() { + port = args[i + 1].parse::().unwrap_or_else(|_| { + eprintln!("Invalid port number: {}", args[i + 1]); + std::process::exit(1); + }); + i += 2; + } else { + eprintln!("-p requires a port number"); + std::process::exit(1); + } + } else if args[i] == "-i" { + if i + 1 < args.len() { + ip = args[i + 1].clone(); + if ip != "127.0.0.1" && ip != "0.0.0.0" { + eprintln!("Invalid IP address: {}. Must be 127.0.0.1 or 0.0.0.0", ip); + std::process::exit(1); + } + i += 2; + } else { + eprintln!("-i requires an IP address"); + std::process::exit(1); + } + } else { + eprintln!("Unknown argument: {}", args[i]); + std::process::exit(1); + } + } + Args { ip, port } +} diff --git a/src/bin/server/handler.rs b/src/bin/server/handler.rs new file mode 100644 index 0000000..906a91f --- /dev/null +++ b/src/bin/server/handler.rs @@ -0,0 +1,15 @@ +use std::io::{BufRead, BufReader, Write}; +use std::net::TcpStream; + +pub fn handle_client(stream: TcpStream) { + println!("Client connected"); + let reader_stream = stream.try_clone().unwrap(); + let reader = BufReader::new(reader_stream); + let mut writer = stream; + for line in reader.lines() { + let line = line.unwrap(); + println!("Received: {}", line); + writeln!(writer, "{}", line).unwrap(); + } + println!("Client disconnected"); +} diff --git a/src/bin/server/main.rs b/src/bin/server/main.rs new file mode 100644 index 0000000..2c6e1dc --- /dev/null +++ b/src/bin/server/main.rs @@ -0,0 +1,23 @@ +mod args; +mod handler; + +use std::net::TcpListener; +use std::thread; + +fn main() { + let args = args::parse_args(); + let listener = match TcpListener::bind(format!("{}:{}", args.ip, args.port)) { + Ok(l) => l, + Err(e) => { + eprintln!("Failed to bind to {}:{}: {}", args.ip, args.port, e); + std::process::exit(1); + } + }; + println!("Server listening on {}", listener.local_addr().unwrap()); + for stream in listener.incoming() { + let stream = stream.unwrap(); + thread::spawn(move || { + handler::handle_client(stream); + }); + } +} diff --git a/src/bin/server/mod.rs b/src/bin/server/mod.rs new file mode 100644 index 0000000..adb60bb --- /dev/null +++ b/src/bin/server/mod.rs @@ -0,0 +1,2 @@ +pub mod args; +pub mod handler; -- cgit v1.2.3