use std::io::{BufRead, BufReader, Write, stdout}; use std::net::TcpStream; use rusqlite::Connection; use std::time::Instant; use crate::connection; use crate::database; use crossterm::{event::{read, Event, KeyCode, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, cursor::{MoveToColumn, Show}, execute}; macro_rules! println { () => { print!("\r\n") }; ($($arg:tt)*) => { print!("\r{}\n", format_args!($($arg)*)) }; } pub struct ClientState { pub stream: Option, pub conn: Connection, pub addr: Option, pub default_port: u16, pub history: Vec, pub history_index: usize, } 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, history: vec![], history_index: 0, } } 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) { enable_raw_mode().unwrap(); 'outer: loop { let mut current_input = String::new(); self.history_index = self.history.len(); redraw(""); loop { match read().unwrap() { Event::Key(key) => match key.code { KeyCode::Up => { if self.history_index > 0 { self.history_index -= 1; current_input = self.history[self.history_index].clone(); redraw(¤t_input); } } KeyCode::Down => { if self.history_index < self.history.len() { self.history_index += 1; current_input = if self.history_index == self.history.len() { "".to_string() } else { self.history[self.history_index].clone() }; redraw(¤t_input); } } KeyCode::Enter => { let line = current_input.trim().to_string(); if !line.is_empty() { self.history.push(line.clone()); } // Process command 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!("\nNo host specified and no previous connection. Usage: connect :"); continue 'outer; } } 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!("\nNot connected. Use 'connect :' first."); continue 'outer; } if parts.len() < 2 { println!("\nUsage: send "); continue 'outer; } 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); if let Err(_) = writeln!(writer, "{}", msg) { self.stream = None; println!("\nConnection lost"); continue 'outer; } let mut response = String::new(); if let Err(_) = response_reader.read_line(&mut response) { self.stream = None; println!("\nConnection lost"); } else { println!("\nEcho: {}", response.trim()); } } "disconnect" => { self.stream = None; println!("\nDisconnected"); } "ping" => { if self.stream.is_none() { println!("\nNot connected. Use 'connect :' first."); continue 'outer; } let s = self.stream.as_mut().unwrap(); let writer_stream = s.try_clone().unwrap(); let mut writer = writer_stream; let start = Instant::now(); let response_reader_stream = s.try_clone().unwrap(); let mut response_reader = BufReader::new(response_reader_stream); if let Err(_) = writeln!(writer, "PING") { self.stream = None; println!("\nConnection lost"); continue 'outer; } let mut response = String::new(); if let Err(_) = response_reader.read_line(&mut response) { self.stream = None; println!("\nConnection lost"); } else { if response.trim() == "PING" { let elapsed = start.elapsed(); println!("\nPong ({:?})", elapsed); } else { self.stream = None; println!("\nConnection lost"); } } } "help" => { help(); } "quit" => break 'outer, _ => help(), } break; } KeyCode::Backspace => { if !current_input.is_empty() { current_input.pop(); redraw(¤t_input); } } KeyCode::Char(c) => { if key.modifiers.contains(KeyModifiers::CONTROL) && c == 'c' { disable_raw_mode().unwrap(); execute!(stdout(), Show).unwrap(); return; } else { current_input.push(c); redraw(¤t_input); } } _ => {} } _ => {} } } } disable_raw_mode().unwrap(); execute!(stdout(), Show).unwrap(); } } fn redraw(current_input: &str) { execute!(stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0)).unwrap(); print!("> {}", current_input); stdout().flush().unwrap(); execute!(stdout(), MoveToColumn((2 + current_input.len()) as u16)).unwrap(); } fn help() { println!("Available commands: connect :, send , disconnect, ping, help, quit"); }