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 | |
| 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-- | Cargo.lock | 171 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | client.db | bin | 0 -> 12288 bytes | |||
| -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 |
14 files changed, 437 insertions, 129 deletions
| @@ -3,6 +3,156 @@ | |||
| 3 | version = 4 | 3 | version = 4 |
| 4 | 4 | ||
| 5 | [[package]] | 5 | [[package]] |
| 6 | name = "ahash" | ||
| 7 | version = "0.8.12" | ||
| 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 9 | checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" | ||
| 10 | dependencies = [ | ||
| 11 | "cfg-if", | ||
| 12 | "once_cell", | ||
| 13 | "version_check", | ||
| 14 | "zerocopy", | ||
| 15 | ] | ||
| 16 | |||
| 17 | [[package]] | ||
| 18 | name = "allocator-api2" | ||
| 19 | version = "0.2.21" | ||
| 20 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 21 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" | ||
| 22 | |||
| 23 | [[package]] | ||
| 24 | name = "bitflags" | ||
| 25 | version = "2.11.0" | ||
| 26 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 27 | checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" | ||
| 28 | |||
| 29 | [[package]] | ||
| 30 | name = "cfg-if" | ||
| 31 | version = "1.0.4" | ||
| 32 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 33 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" | ||
| 34 | |||
| 35 | [[package]] | ||
| 36 | name = "fallible-iterator" | ||
| 37 | version = "0.2.0" | ||
| 38 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 39 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" | ||
| 40 | |||
| 41 | [[package]] | ||
| 42 | name = "fallible-streaming-iterator" | ||
| 43 | version = "0.1.9" | ||
| 44 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 45 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" | ||
| 46 | |||
| 47 | [[package]] | ||
| 48 | name = "hashbrown" | ||
| 49 | version = "0.14.5" | ||
| 50 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 51 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" | ||
| 52 | dependencies = [ | ||
| 53 | "ahash", | ||
| 54 | "allocator-api2", | ||
| 55 | ] | ||
| 56 | |||
| 57 | [[package]] | ||
| 58 | name = "hashlink" | ||
| 59 | version = "0.8.4" | ||
| 60 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 61 | checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" | ||
| 62 | dependencies = [ | ||
| 63 | "hashbrown", | ||
| 64 | ] | ||
| 65 | |||
| 66 | [[package]] | ||
| 67 | name = "libsqlite3-sys" | ||
| 68 | version = "0.26.0" | ||
| 69 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 70 | checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" | ||
| 71 | dependencies = [ | ||
| 72 | "pkg-config", | ||
| 73 | "vcpkg", | ||
| 74 | ] | ||
| 75 | |||
| 76 | [[package]] | ||
| 77 | name = "once_cell" | ||
| 78 | version = "1.21.3" | ||
| 79 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 80 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" | ||
| 81 | |||
| 82 | [[package]] | ||
| 83 | name = "pkg-config" | ||
| 84 | version = "0.3.32" | ||
| 85 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 86 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" | ||
| 87 | |||
| 88 | [[package]] | ||
| 89 | name = "proc-macro2" | ||
| 90 | version = "1.0.106" | ||
| 91 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 92 | checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" | ||
| 93 | dependencies = [ | ||
| 94 | "unicode-ident", | ||
| 95 | ] | ||
| 96 | |||
| 97 | [[package]] | ||
| 98 | name = "quote" | ||
| 99 | version = "1.0.44" | ||
| 100 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 101 | checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" | ||
| 102 | dependencies = [ | ||
| 103 | "proc-macro2", | ||
| 104 | ] | ||
| 105 | |||
| 106 | [[package]] | ||
| 107 | name = "rusqlite" | ||
| 108 | version = "0.29.0" | ||
| 109 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 110 | checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" | ||
| 111 | dependencies = [ | ||
| 112 | "bitflags", | ||
| 113 | "fallible-iterator", | ||
| 114 | "fallible-streaming-iterator", | ||
| 115 | "hashlink", | ||
| 116 | "libsqlite3-sys", | ||
| 117 | "smallvec", | ||
| 118 | ] | ||
| 119 | |||
| 120 | [[package]] | ||
| 121 | name = "smallvec" | ||
| 122 | version = "1.15.1" | ||
| 123 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 124 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" | ||
| 125 | |||
| 126 | [[package]] | ||
| 127 | name = "syn" | ||
| 128 | version = "2.0.117" | ||
| 129 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 130 | checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" | ||
| 131 | dependencies = [ | ||
| 132 | "proc-macro2", | ||
| 133 | "quote", | ||
| 134 | "unicode-ident", | ||
| 135 | ] | ||
| 136 | |||
| 137 | [[package]] | ||
| 138 | name = "unicode-ident" | ||
| 139 | version = "1.0.24" | ||
| 140 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 141 | checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" | ||
| 142 | |||
| 143 | [[package]] | ||
| 144 | name = "vcpkg" | ||
| 145 | version = "0.2.15" | ||
| 146 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 147 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" | ||
| 148 | |||
| 149 | [[package]] | ||
| 150 | name = "version_check" | ||
| 151 | version = "0.9.5" | ||
| 152 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 153 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" | ||
| 154 | |||
| 155 | [[package]] | ||
| 6 | name = "winapi" | 156 | name = "winapi" |
| 7 | version = "0.3.9" | 157 | version = "0.3.9" |
| 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | 158 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -28,5 +178,26 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | |||
| 28 | name = "zagan-bet" | 178 | name = "zagan-bet" |
| 29 | version = "0.1.0" | 179 | version = "0.1.0" |
| 30 | dependencies = [ | 180 | dependencies = [ |
| 181 | "rusqlite", | ||
| 31 | "winapi", | 182 | "winapi", |
| 32 | ] | 183 | ] |
| 184 | |||
| 185 | [[package]] | ||
| 186 | name = "zerocopy" | ||
| 187 | version = "0.8.40" | ||
| 188 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 189 | checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" | ||
| 190 | dependencies = [ | ||
| 191 | "zerocopy-derive", | ||
| 192 | ] | ||
| 193 | |||
| 194 | [[package]] | ||
| 195 | name = "zerocopy-derive" | ||
| 196 | version = "0.8.40" | ||
| 197 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 198 | checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" | ||
| 199 | dependencies = [ | ||
| 200 | "proc-macro2", | ||
| 201 | "quote", | ||
| 202 | "syn", | ||
| 203 | ] | ||
| @@ -4,4 +4,5 @@ version = "0.1.0" | |||
| 4 | edition = "2024" | 4 | edition = "2024" |
| 5 | 5 | ||
| 6 | [dependencies] | 6 | [dependencies] |
| 7 | rusqlite = "0.29" | ||
| 7 | winapi = { version = "0.3", features = ["consoleapi", "processenv", "wincon", "minwindef", "winbase"] } | 8 | winapi = { version = "0.3", features = ["consoleapi", "processenv", "wincon", "minwindef", "winbase"] } |
diff --git a/client.db b/client.db new file mode 100644 index 0000000..8ead0e2 --- /dev/null +++ b/client.db | |||
| Binary files differ | |||
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; | ||
