diff options
| author | salmonik <raisersoft@proton.me> | 2026-02-28 17:35:40 +0100 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-02-28 17:35:40 +0100 |
| commit | 28382b249535e964ce983fe5a0821a898a077775 (patch) | |
| tree | 3b68dd13d9fec27ab3163f09285c718874540c65 /src | |
| parent | init version (diff) | |
| download | zagan-bet-28382b249535e964ce983fe5a0821a898a077775.tar.gz zagan-bet-28382b249535e964ce983fe5a0821a898a077775.zip | |
multi file for both sides
Diffstat (limited to '')
| -rw-r--r-- | src/bin/client.rs | 98 | ||||
| -rw-r--r-- | src/bin/client/connection.rs | 32 | ||||
| -rw-r--r-- | src/bin/client/database.rs | 30 | ||||
| -rw-r--r-- | src/bin/client/main.rs | 17 | ||||
| -rw-r--r-- | src/bin/client/mod.rs | 4 | ||||
| -rw-r--r-- | src/bin/client/platform.rs | 28 | ||||
| -rw-r--r-- | src/bin/client/repl.rs | 108 | ||||
| -rw-r--r-- | src/bin/server/args.rs (renamed from src/bin/server.rs) | 37 | ||||
| -rw-r--r-- | src/bin/server/handler.rs | 15 | ||||
| -rw-r--r-- | src/bin/server/main.rs | 23 | ||||
| -rw-r--r-- | src/bin/server/mod.rs | 2 |
11 files changed, 265 insertions, 129 deletions
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 @@ | |||
| 1 | use std::io::{BufRead, BufReader, Write, stdin, stdout}; | ||
| 2 | use std::net::TcpStream; | ||
| 3 | use std::time::Instant; | ||
| 4 | |||
| 5 | #[cfg(windows)] | ||
| 6 | mod win { | ||
| 7 | use std::io; | ||
| 8 | use std::os::windows::io::AsRawHandle; | ||
| 9 | use winapi::um::consoleapi::GetConsoleMode; | ||
| 10 | use winapi::um::consoleapi::SetConsoleMode; | ||
| 11 | use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; | ||
| 12 | use winapi::um::winbase::STD_OUTPUT_HANDLE; | ||
| 13 | use winapi::um::processenv::GetStdHandle; | ||
| 14 | use winapi::shared::minwindef::DWORD; | ||
| 15 | |||
| 16 | pub fn enable_vt() -> io::Result<()> { | ||
| 17 | unsafe { | ||
| 18 | let h = GetStdHandle(STD_OUTPUT_HANDLE); | ||
| 19 | if h.is_null() { return Err(io::Error::last_os_error()); } | ||
| 20 | |||
| 21 | let mut mode: DWORD = 0; | ||
| 22 | if GetConsoleMode(h, &mut mode) == 0 { | ||
| 23 | return Err(io::Error::last_os_error()); | ||
| 24 | } | ||
| 25 | let new = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING; | ||
| 26 | if SetConsoleMode(h, new) == 0 { | ||
| 27 | return Err(io::Error::last_os_error()); | ||
| 28 | } | ||
| 29 | Ok(()) | ||
| 30 | } | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | fn main() { | ||
| 35 | #[cfg(windows)] | ||
| 36 | win::enable_vt().unwrap(); | ||
| 37 | |||
| 38 | let mut addr: Option<String>; | ||
| 39 | let mut stream: Option<TcpStream> = None; | ||
| 40 | loop { | ||
| 41 | print!("Command: "); | ||
| 42 | stdout().flush().unwrap(); | ||
| 43 | let mut line = String::new(); | ||
| 44 | stdin().read_line(&mut line).unwrap(); | ||
| 45 | let line = line.trim(); | ||
| 46 | if line.is_empty() { | ||
| 47 | continue; | ||
| 48 | } | ||
| 49 | let parts: Vec<&str> = line.splitn(2, ' ').collect(); | ||
| 50 | match parts[0] { | ||
| 51 | "connect" => { | ||
| 52 | if parts.len() < 2 { | ||
| 53 | println!("Usage: connect <addr>"); | ||
| 54 | continue; | ||
| 55 | } | ||
| 56 | addr = Some(parts[1].to_string()); | ||
| 57 | match TcpStream::connect(addr.as_ref().unwrap()) { | ||
| 58 | Ok(s) => { | ||
| 59 | let mut ping_writer = s.try_clone().unwrap(); | ||
| 60 | let mut ping_reader = BufReader::new(s.try_clone().unwrap()); | ||
| 61 | let start = Instant::now(); | ||
| 62 | writeln!(ping_writer, "PING").unwrap(); | ||
| 63 | let mut response = String::new(); | ||
| 64 | ping_reader.read_line(&mut response).unwrap(); | ||
| 65 | let elapsed = start.elapsed(); | ||
| 66 | println!("Connected to {} with ping delay: {:?}", addr.as_ref().unwrap(), elapsed); | ||
| 67 | stream = Some(s); | ||
| 68 | } | ||
| 69 | Err(e) => { | ||
| 70 | println!("Failed to connect: {}", e); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | "send" => { | ||
| 75 | if stream.is_none() { | ||
| 76 | println!("Not connected. Use 'connect <addr>' first."); | ||
| 77 | continue; | ||
| 78 | } | ||
| 79 | if parts.len() < 2 { | ||
| 80 | println!("Usage: send <message>"); | ||
| 81 | continue; | ||
| 82 | } | ||
| 83 | let msg = parts[1]; | ||
| 84 | let s = stream.as_mut().unwrap(); | ||
| 85 | let writer_stream = s.try_clone().unwrap(); | ||
| 86 | let mut writer = writer_stream; | ||
| 87 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 88 | let mut response_reader = BufReader::new(response_reader_stream); | ||
| 89 | writeln!(writer, "{}", msg).unwrap(); | ||
| 90 | let mut response = String::new(); | ||
| 91 | response_reader.read_line(&mut response).unwrap(); | ||
| 92 | println!("Echo: {}", response.trim()); | ||
| 93 | } | ||
| 94 | "quit" => break, | ||
| 95 | _ => println!("Unknown command. Available: connect, send, quit"), | ||
| 96 | } | ||
| 97 | } | ||
| 98 | } | ||
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 @@ | |||
| 1 | use std::io::{BufRead, BufReader, Write}; | ||
| 2 | use std::net::TcpStream; | ||
| 3 | use std::sync::mpsc; | ||
| 4 | use std::thread; | ||
| 5 | use std::time::{Duration, Instant}; | ||
| 6 | |||
| 7 | pub fn connect(addr: &str) -> Result<TcpStream, Box<dyn std::error::Error>> { | ||
| 8 | let (tx, rx) = mpsc::channel(); | ||
| 9 | let addr_str = addr.to_string(); | ||
| 10 | thread::spawn(move || { | ||
| 11 | match TcpStream::connect(&addr_str) { | ||
| 12 | Ok(s) => tx.send(Ok(s)).unwrap(), | ||
| 13 | Err(e) => tx.send(Err(e)).unwrap(), | ||
| 14 | } | ||
| 15 | }); | ||
| 16 | match rx.recv_timeout(Duration::from_secs(3)) { | ||
| 17 | Ok(Ok(s)) => { | ||
| 18 | let mut ping_writer = s.try_clone().unwrap(); | ||
| 19 | let mut ping_reader = BufReader::new(s.try_clone().unwrap()); | ||
| 20 | let start = Instant::now(); | ||
| 21 | writeln!(ping_writer, "PING").unwrap(); | ||
| 22 | let mut response = String::new(); | ||
| 23 | ping_reader.read_line(&mut response).unwrap(); | ||
| 24 | let elapsed = start.elapsed(); | ||
| 25 | println!("Connected to {} with ping delay: {:?}", addr, elapsed); | ||
| 26 | Ok(s) | ||
| 27 | } | ||
| 28 | Ok(Err(e)) => Err(Box::new(e)), | ||
| 29 | Err(mpsc::RecvTimeoutError::Timeout) => Err("Connection timed out after 3 seconds".into()), | ||
| 30 | Err(_) => Err("Connection attempt failed".into()), | ||
| 31 | } | ||
| 32 | } | ||
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 @@ | |||
| 1 | use rusqlite::Connection; | ||
| 2 | |||
| 3 | pub fn init_db() -> Connection { | ||
| 4 | let conn = Connection::open("client.db").unwrap(); | ||
| 5 | conn.execute( | ||
| 6 | "CREATE TABLE IF NOT EXISTS hosts ( | ||
| 7 | host TEXT PRIMARY KEY, | ||
| 8 | last_used DATETIME DEFAULT CURRENT_TIMESTAMP | ||
| 9 | )", | ||
| 10 | [], | ||
| 11 | ).unwrap(); | ||
| 12 | conn | ||
| 13 | } | ||
| 14 | |||
| 15 | pub fn get_last_host(conn: &Connection) -> Option<String> { | ||
| 16 | let mut stmt = conn.prepare("SELECT host FROM hosts ORDER BY last_used DESC LIMIT 1").unwrap(); | ||
| 17 | let mut rows = stmt.query([]).unwrap(); | ||
| 18 | if let Ok(Some(row)) = rows.next() { | ||
| 19 | row.get(0).ok() | ||
| 20 | } else { | ||
| 21 | None | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | pub fn save_host(conn: &Connection, host: &str) { | ||
| 26 | conn.execute( | ||
| 27 | "INSERT OR REPLACE INTO hosts (host, last_used) VALUES (?1, CURRENT_TIMESTAMP)", | ||
| 28 | [host], | ||
| 29 | ).unwrap(); | ||
| 30 | } | ||
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 @@ | |||
| 1 | mod repl; | ||
| 2 | mod database; | ||
| 3 | mod connection; | ||
| 4 | mod platform; | ||
| 5 | |||
| 6 | use repl::ClientState; | ||
| 7 | |||
| 8 | fn main() { | ||
| 9 | #[cfg(windows)] | ||
| 10 | platform::win::enable_vt().unwrap(); | ||
| 11 | |||
| 12 | let mut state = ClientState::new(); | ||
| 13 | if let Some(host) = state.addr.clone() { | ||
| 14 | state.connect_to(&host); | ||
| 15 | } | ||
| 16 | state.run(); | ||
| 17 | } | ||
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 @@ | |||
| 1 | pub mod database; | ||
| 2 | pub mod connection; | ||
| 3 | pub mod platform; | ||
| 4 | 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 @@ | |||
| 1 | #[cfg(windows)] | ||
| 2 | pub mod win { | ||
| 3 | use std::io; | ||
| 4 | use std::os::windows::io::AsRawHandle; | ||
| 5 | use winapi::um::consoleapi::GetConsoleMode; | ||
| 6 | use winapi::um::consoleapi::SetConsoleMode; | ||
| 7 | use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; | ||
| 8 | use winapi::um::winbase::STD_OUTPUT_HANDLE; | ||
| 9 | use winapi::um::processenv::GetStdHandle; | ||
| 10 | use winapi::shared::minwindef::DWORD; | ||
| 11 | |||
| 12 | pub fn enable_vt() -> io::Result<()> { | ||
| 13 | unsafe { | ||
| 14 | let h = GetStdHandle(STD_OUTPUT_HANDLE); | ||
| 15 | if h.is_null() { return Err(io::Error::last_os_error()); } | ||
| 16 | |||
| 17 | let mut mode: DWORD = 0; | ||
| 18 | if GetConsoleMode(h, &mut mode) == 0 { | ||
| 19 | return Err(io::Error::last_os_error()); | ||
| 20 | } | ||
| 21 | let new = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING; | ||
| 22 | if SetConsoleMode(h, new) == 0 { | ||
| 23 | return Err(io::Error::last_os_error()); | ||
| 24 | } | ||
| 25 | Ok(()) | ||
| 26 | } | ||
| 27 | } | ||
| 28 | } | ||
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 @@ | |||
| 1 | use std::io::{BufRead, BufReader, Write, stdin, stdout}; | ||
| 2 | use std::net::TcpStream; | ||
| 3 | use rusqlite::Connection; | ||
| 4 | |||
| 5 | use crate::connection; | ||
| 6 | use crate::database; | ||
| 7 | |||
| 8 | pub struct ClientState { | ||
| 9 | pub stream: Option<TcpStream>, | ||
| 10 | pub conn: Connection, | ||
| 11 | pub addr: Option<String>, | ||
| 12 | pub default_port: u16, | ||
| 13 | } | ||
| 14 | |||
| 15 | impl ClientState { | ||
| 16 | pub fn new() -> Self { | ||
| 17 | let conn = database::init_db(); | ||
| 18 | let default_ip = "127.0.0.1".to_string(); | ||
| 19 | let default_port = 11525; | ||
| 20 | let last_host = database::get_last_host(&conn); | ||
| 21 | let default_host = format!("{}:{}", default_ip, default_port); | ||
| 22 | let addr = last_host.or(Some(default_host)); | ||
| 23 | |||
| 24 | Self { | ||
| 25 | stream: None, | ||
| 26 | conn, | ||
| 27 | addr, | ||
| 28 | default_port, | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | pub fn connect_to(&mut self, addr: &str) { | ||
| 33 | self.addr = Some(addr.to_string()); | ||
| 34 | match connection::connect(addr) { | ||
| 35 | Ok(s) => { | ||
| 36 | self.stream = Some(s); | ||
| 37 | database::save_host(&self.conn, addr); | ||
| 38 | } | ||
| 39 | Err(e) => println!("Failed to connect: {}", e), | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | pub fn run(&mut self) { | ||
| 44 | loop { | ||
| 45 | print!("> "); | ||
| 46 | stdout().flush().unwrap(); | ||
| 47 | let mut line = String::new(); | ||
| 48 | stdin().read_line(&mut line).unwrap(); | ||
| 49 | let line = line.trim(); | ||
| 50 | if line.is_empty() { | ||
| 51 | continue; | ||
| 52 | } | ||
| 53 | let parts: Vec<&str> = line.splitn(2, ' ').collect(); | ||
| 54 | match parts[0] { | ||
| 55 | "connect" => { | ||
| 56 | let addr_str = if parts.len() < 2 { | ||
| 57 | if let Some(h) = &self.addr { | ||
| 58 | h.clone() | ||
| 59 | } else { | ||
| 60 | println!("No host specified and no previous connection. Usage: connect <ip>:<port>"); | ||
| 61 | continue; | ||
| 62 | } | ||
| 63 | } else { | ||
| 64 | let mut a = parts[1].to_string(); | ||
| 65 | if !a.contains(':') { | ||
| 66 | a = format!("{}:{}", a, self.default_port); | ||
| 67 | } | ||
| 68 | a | ||
| 69 | }; | ||
| 70 | self.connect_to(&addr_str); | ||
| 71 | } | ||
| 72 | "send" => { | ||
| 73 | if self.stream.is_none() { | ||
| 74 | println!("Not connected. Use 'connect <ip>:<port>' first."); | ||
| 75 | continue; | ||
| 76 | } | ||
| 77 | if parts.len() < 2 { | ||
| 78 | println!("Usage: send <message>"); | ||
| 79 | continue; | ||
| 80 | } | ||
| 81 | let msg = parts[1]; | ||
| 82 | let s = self.stream.as_mut().unwrap(); | ||
| 83 | let writer_stream = s.try_clone().unwrap(); | ||
| 84 | let mut writer = writer_stream; | ||
| 85 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 86 | let mut response_reader = BufReader::new(response_reader_stream); | ||
| 87 | writeln!(writer, "{}", msg).unwrap(); | ||
| 88 | let mut response = String::new(); | ||
| 89 | response_reader.read_line(&mut response).unwrap(); | ||
| 90 | println!("Echo: {}", response.trim()); | ||
| 91 | } | ||
| 92 | "disconnect" => { | ||
| 93 | self.stream = None; | ||
| 94 | println!("Disconnected"); | ||
| 95 | } | ||
| 96 | "help" => { | ||
| 97 | help(); | ||
| 98 | } | ||
| 99 | "quit" => break, | ||
| 100 | _ => help(), | ||
| 101 | } | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | fn help() { | ||
| 107 | println!("Available commands: connect <ip>:<port>, send <message>, disconnect, help, quit"); | ||
| 108 | } | ||
diff --git a/src/bin/server.rs b/src/bin/server/args.rs index 215d2ae..31c0acc 100644 --- a/src/bin/server.rs +++ b/src/bin/server/args.rs | |||
| @@ -1,24 +1,12 @@ | |||
| 1 | use std::io::{BufRead, BufReader, Write}; | 1 | pub struct Args { |
| 2 | use std::net::TcpListener; | 2 | pub ip: String, |
| 3 | use std::thread; | 3 | pub port: u16, |
| 4 | |||
| 5 | fn handle_client(stream: std::net::TcpStream) { | ||
| 6 | println!("Client connected"); | ||
| 7 | let reader_stream = stream.try_clone().unwrap(); | ||
| 8 | let reader = BufReader::new(reader_stream); | ||
| 9 | let mut writer = stream; | ||
| 10 | for line in reader.lines() { | ||
| 11 | let line = line.unwrap(); | ||
| 12 | println!("Received: {}", line); | ||
| 13 | writeln!(writer, "{}", line).unwrap(); | ||
| 14 | } | ||
| 15 | println!("Client disconnected"); | ||
| 16 | } | 4 | } |
| 17 | 5 | ||
| 18 | fn main() { | 6 | pub fn parse_args() -> Args { |
| 19 | let args: Vec<String> = std::env::args().collect(); | 7 | let args: Vec<String> = std::env::args().collect(); |
| 20 | let mut ip = "127.0.0.1".to_string(); | 8 | let mut ip = "127.0.0.1".to_string(); |
| 21 | let mut port = 8080; | 9 | let mut port = 11525; |
| 22 | let mut i = 1; | 10 | let mut i = 1; |
| 23 | while i < args.len() { | 11 | while i < args.len() { |
| 24 | if args[i] == "-p" { | 12 | if args[i] == "-p" { |
| @@ -49,18 +37,5 @@ fn main() { | |||
| 49 | std::process::exit(1); | 37 | std::process::exit(1); |
| 50 | } | 38 | } |
| 51 | } | 39 | } |
| 52 | let listener = match TcpListener::bind(format!("{}:{}", ip, port)) { | 40 | Args { ip, port } |
| 53 | Ok(l) => l, | ||
| 54 | Err(e) => { | ||
| 55 | eprintln!("Failed to bind to {}:{}: {}", ip, port, e); | ||
| 56 | std::process::exit(1); | ||
| 57 | } | ||
| 58 | }; | ||
| 59 | println!("Server listening on {}", listener.local_addr().unwrap()); | ||
| 60 | for stream in listener.incoming() { | ||
| 61 | let stream = stream.unwrap(); | ||
| 62 | thread::spawn(move || { | ||
| 63 | handle_client(stream); | ||
| 64 | }); | ||
| 65 | } | ||
| 66 | } | 41 | } |
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 @@ | |||
| 1 | use std::io::{BufRead, BufReader, Write}; | ||
| 2 | use std::net::TcpStream; | ||
| 3 | |||
| 4 | pub fn handle_client(stream: TcpStream) { | ||
| 5 | println!("Client connected"); | ||
| 6 | let reader_stream = stream.try_clone().unwrap(); | ||
| 7 | let reader = BufReader::new(reader_stream); | ||
| 8 | let mut writer = stream; | ||
| 9 | for line in reader.lines() { | ||
| 10 | let line = line.unwrap(); | ||
| 11 | println!("Received: {}", line); | ||
| 12 | writeln!(writer, "{}", line).unwrap(); | ||
| 13 | } | ||
| 14 | println!("Client disconnected"); | ||
| 15 | } | ||
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 @@ | |||
| 1 | mod args; | ||
| 2 | mod handler; | ||
| 3 | |||
| 4 | use std::net::TcpListener; | ||
| 5 | use std::thread; | ||
| 6 | |||
| 7 | fn main() { | ||
| 8 | let args = args::parse_args(); | ||
| 9 | let listener = match TcpListener::bind(format!("{}:{}", args.ip, args.port)) { | ||
| 10 | Ok(l) => l, | ||
| 11 | Err(e) => { | ||
| 12 | eprintln!("Failed to bind to {}:{}: {}", args.ip, args.port, e); | ||
| 13 | std::process::exit(1); | ||
| 14 | } | ||
| 15 | }; | ||
| 16 | println!("Server listening on {}", listener.local_addr().unwrap()); | ||
| 17 | for stream in listener.incoming() { | ||
| 18 | let stream = stream.unwrap(); | ||
| 19 | thread::spawn(move || { | ||
| 20 | handler::handle_client(stream); | ||
| 21 | }); | ||
| 22 | } | ||
| 23 | } | ||
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 @@ | |||
| 1 | pub mod args; | ||
| 2 | pub mod handler; | ||
