summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/client/connection.rs2
-rw-r--r--src/bin/client/repl.rs215
2 files changed, 161 insertions, 56 deletions
diff --git a/src/bin/client/connection.rs b/src/bin/client/connection.rs
index 9e402cb..d03dc1a 100644
--- a/src/bin/client/connection.rs
+++ b/src/bin/client/connection.rs
@@ -22,7 +22,7 @@ pub fn connect(addr: &str) -> Result<TcpStream, Box<dyn std::error::Error>> {
22 let mut response = String::new(); 22 let mut response = String::new();
23 ping_reader.read_line(&mut response).unwrap(); 23 ping_reader.read_line(&mut response).unwrap();
24 let elapsed = start.elapsed(); 24 let elapsed = start.elapsed();
25 println!("Connected to {} with ping delay: {:?}", addr, elapsed); 25 println!("\n\rConnected to {} with ping delay: {:?}", addr, elapsed);
26 Ok(s) 26 Ok(s)
27 } 27 }
28 Ok(Err(e)) => Err(Box::new(e)), 28 Ok(Err(e)) => Err(Box::new(e)),
diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs
index d38c54d..1512091 100644
--- a/src/bin/client/repl.rs
+++ b/src/bin/client/repl.rs
@@ -1,15 +1,29 @@
1use std::io::{BufRead, BufReader, Write, stdin, stdout}; 1use std::io::{BufRead, BufReader, Write, stdout};
2use std::net::TcpStream; 2use std::net::TcpStream;
3use rusqlite::Connection; 3use rusqlite::Connection;
4use std::time::Instant;
4 5
5use crate::connection; 6use crate::connection;
6use crate::database; 7use crate::database;
7 8
9use crossterm::{event::{read, Event, KeyCode, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, cursor::{MoveToColumn, Show}, execute};
10
11macro_rules! println {
12 () => {
13 print!("\r\n")
14 };
15 ($($arg:tt)*) => {
16 print!("\r{}\n", format_args!($($arg)*))
17 };
18}
19
8pub struct ClientState { 20pub struct ClientState {
9 pub stream: Option<TcpStream>, 21 pub stream: Option<TcpStream>,
10 pub conn: Connection, 22 pub conn: Connection,
11 pub addr: Option<String>, 23 pub addr: Option<String>,
12 pub default_port: u16, 24 pub default_port: u16,
25 pub history: Vec<String>,
26 pub history_index: usize,
13} 27}
14 28
15impl ClientState { 29impl ClientState {
@@ -26,6 +40,8 @@ impl ClientState {
26 conn, 40 conn,
27 addr, 41 addr,
28 default_port, 42 default_port,
43 history: vec![],
44 history_index: 0,
29 } 45 }
30 } 46 }
31 47
@@ -41,68 +57,157 @@ impl ClientState {
41 } 57 }
42 58
43 pub fn run(&mut self) { 59 pub fn run(&mut self) {
44 loop { 60 enable_raw_mode().unwrap();
45 print!("> "); 61 'outer: loop {
46 stdout().flush().unwrap(); 62 let mut current_input = String::new();
47 let mut line = String::new(); 63 self.history_index = self.history.len();
48 stdin().read_line(&mut line).unwrap(); 64 redraw("");
49 let line = line.trim(); 65 loop {
50 if line.is_empty() { 66 match read().unwrap() {
51 continue; 67 Event::Key(key) => match key.code {
52 } 68 KeyCode::Up => {
53 let parts: Vec<&str> = line.splitn(2, ' ').collect(); 69 if self.history_index > 0 {
54 match parts[0] { 70 self.history_index -= 1;
55 "connect" => { 71 current_input = self.history[self.history_index].clone();
56 let addr_str = if parts.len() < 2 { 72 redraw(&current_input);
57 if let Some(h) = &self.addr { 73 }
58 h.clone()
59 } else {
60 println!("No host specified and no previous connection. Usage: connect <ip>:<port>");
61 continue;
62 } 74 }
63 } else { 75 KeyCode::Down => {
64 let mut a = parts[1].to_string(); 76 if self.history_index < self.history.len() {
65 if !a.contains(':') { 77 self.history_index += 1;
66 a = format!("{}:{}", a, self.default_port); 78 current_input = if self.history_index == self.history.len() { "".to_string() } else { self.history[self.history_index].clone() };
79 redraw(&current_input);
80 }
67 } 81 }
68 a 82 KeyCode::Enter => {
69 }; 83 let line = current_input.trim().to_string();
70 self.connect_to(&addr_str); 84 if !line.is_empty() {
71 } 85 self.history.push(line.clone());
72 "send" => { 86 }
73 if self.stream.is_none() { 87 // Process command
74 println!("Not connected. Use 'connect <ip>:<port>' first."); 88 let parts: Vec<&str> = line.splitn(2, ' ').collect();
75 continue; 89 match parts[0] {
76 } 90 "connect" => {
77 if parts.len() < 2 { 91 let addr_str = if parts.len() < 2 {
78 println!("Usage: send <message>"); 92 if let Some(h) = &self.addr {
79 continue; 93 h.clone()
94 } else {
95 println!("\nNo host specified and no previous connection. Usage: connect <ip>:<port>");
96 continue 'outer;
97 }
98 } else {
99 let mut a = parts[1].to_string();
100 if !a.contains(':') {
101 a = format!("{}:{}", a, self.default_port);
102 }
103 a
104 };
105 self.connect_to(&addr_str);
106 }
107 "send" => {
108 if self.stream.is_none() {
109 println!("\nNot connected. Use 'connect <ip>:<port>' first.");
110 continue 'outer;
111 }
112 if parts.len() < 2 {
113 println!("\nUsage: send <message>");
114 continue 'outer;
115 }
116 let msg = parts[1];
117 let s = self.stream.as_mut().unwrap();
118 let writer_stream = s.try_clone().unwrap();
119 let mut writer = writer_stream;
120 let response_reader_stream = s.try_clone().unwrap();
121 let mut response_reader = BufReader::new(response_reader_stream);
122 if let Err(_) = writeln!(writer, "{}", msg) {
123 self.stream = None;
124 println!("\nConnection lost");
125 continue 'outer;
126 }
127 let mut response = String::new();
128 if let Err(_) = response_reader.read_line(&mut response) {
129 self.stream = None;
130 println!("\nConnection lost");
131 } else {
132 println!("\nEcho: {}", response.trim());
133 }
134 }
135 "disconnect" => {
136 self.stream = None;
137 println!("\nDisconnected");
138 }
139 "ping" => {
140 if self.stream.is_none() {
141 println!("\nNot connected. Use 'connect <ip>:<port>' first.");
142 continue 'outer;
143 }
144 let s = self.stream.as_mut().unwrap();
145 let writer_stream = s.try_clone().unwrap();
146 let mut writer = writer_stream;
147 let start = Instant::now();
148 let response_reader_stream = s.try_clone().unwrap();
149 let mut response_reader = BufReader::new(response_reader_stream);
150 if let Err(_) = writeln!(writer, "PING") {
151 self.stream = None;
152 println!("\nConnection lost");
153 continue 'outer;
154 }
155 let mut response = String::new();
156 if let Err(_) = response_reader.read_line(&mut response) {
157 self.stream = None;
158 println!("\nConnection lost");
159 } else {
160 if response.trim() == "PING" {
161 let elapsed = start.elapsed();
162 println!("\nPong ({:?})", elapsed);
163 } else {
164 self.stream = None;
165 println!("\nConnection lost");
166 }
167 }
168 }
169 "help" => {
170 help();
171 }
172 "quit" => break 'outer,
173 _ => help(),
174 }
175 break;
176 }
177 KeyCode::Backspace => {
178 if !current_input.is_empty() {
179 current_input.pop();
180 redraw(&current_input);
181 }
182 }
183 KeyCode::Char(c) => {
184 if key.modifiers.contains(KeyModifiers::CONTROL) && c == 'c' {
185 disable_raw_mode().unwrap();
186 execute!(stdout(), Show).unwrap();
187 return;
188 } else {
189 current_input.push(c);
190 redraw(&current_input);
191 }
192 }
193 _ => {}
80 } 194 }
81 let msg = parts[1]; 195 _ => {}
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 } 196 }
96 "help" => {
97 help();
98 }
99 "quit" => break,
100 _ => help(),
101 } 197 }
102 } 198 }
199 disable_raw_mode().unwrap();
200 execute!(stdout(), Show).unwrap();
103 } 201 }
104} 202}
105 203
204fn redraw(current_input: &str) {
205 execute!(stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0)).unwrap();
206 print!("> {}", current_input);
207 stdout().flush().unwrap();
208 execute!(stdout(), MoveToColumn((2 + current_input.len()) as u16)).unwrap();
209}
210
106fn help() { 211fn help() {
107 println!("Available commands: connect <ip>:<port>, send <message>, disconnect, help, quit"); 212 println!("Available commands: connect <ip>:<port>, send <message>, disconnect, ping, help, quit");
108} 213}