rust_chat/
user.rs

1use std::{
2    io::{self, BufRead, Write},
3    net,
4    sync::{Arc, Mutex},
5    thread,
6};
7
8use crossterm::{
9    cursor,
10    event::{read, Event, KeyCode, KeyEvent},
11    style::Stylize,
12    terminal, Command, ExecutableCommand,
13};
14use notify_rust::Notification;
15
16use crate::utils::{backspace, get_formatted_time, input, print, println};
17
18pub struct User {
19    name: String,
20    stream: net::TcpStream,
21    current_msg: Arc<Mutex<String>>,
22}
23
24impl User {
25    pub fn new_get_name(stream: net::TcpStream) -> User {
26        let name = loop {
27            let mut name = input("Enter your name: ");
28            name = name.trim().to_string();
29
30            if name.contains(";") {
31                println!("Name cannot contain ';'");
32                continue;
33            }
34
35            if name.len() > 0 {
36                break name;
37            }
38        };
39
40        let current_msg = Arc::new(Mutex::new(String::new()));
41        User {
42            name,
43            stream,
44            current_msg,
45        }
46    }
47
48    fn send(&self, msg: &str) {
49        let msg = format!("{};{}\n", self.name, msg);
50        let mut stream = self.stream.try_clone().unwrap();
51
52        stream.write(msg.as_bytes()).unwrap();
53    }
54
55    fn watch_output(&self) {
56        let mut reader = io::BufReader::new(&self.stream);
57
58        loop {
59            let mut msg = String::new();
60            reader.read_line(&mut msg).unwrap();
61            io::stdout()
62                .execute(terminal::Clear(terminal::ClearType::CurrentLine))
63                .unwrap();
64
65            if msg.len() == 0 {
66                break;
67            }
68
69            let (name, msg) = msg.split_once(";").unwrap();
70            Notification::new()
71                .summary(name)
72                .body(msg)
73                .appname("Rust Chat")
74                .show()
75                .ok();
76
77            let name = name.red();
78            let time = get_formatted_time().red();
79            print(&format!("\r[{}] {}: {}", time, name, msg));
80            self.print_current_msg();
81        }
82    }
83
84    fn watch_input(&self) {
85        fn move_cursor(cmd: impl Command) {
86            io::stdout().execute(cmd).unwrap();
87        }
88
89        loop {
90            self.print_current_msg();
91            let key_code;
92
93            match read().unwrap() {
94                Event::Key(KeyEvent { code, .. }) => key_code = code,
95                _ => continue,
96            }
97
98            let mut current_msg = self.current_msg.lock().unwrap();
99            match key_code {
100                KeyCode::Char(code) => current_msg.push(code),
101                KeyCode::Left => move_cursor(cursor::MoveLeft(1)),
102                KeyCode::Right => move_cursor(cursor::MoveRight(1)),
103                KeyCode::Backspace => {
104                    backspace();
105                    current_msg.pop();
106                }
107                KeyCode::Enter if current_msg.to_string() == "exit" => break,
108                KeyCode::Enter if current_msg.len() == 0 => continue,
109                KeyCode::Enter => {
110                    drop(current_msg); // unlock mutex
111                    self.print_current_complete_msg();
112                    println("");
113
114                    let mut current_msg = self.current_msg.lock().unwrap();
115                    self.send(&current_msg);
116                    current_msg.clear();
117                }
118                _ => continue,
119            }
120        }
121    }
122
123    fn print_current_msg(&self) {
124        let name = self.name.as_str().green();
125        let msg = self.current_msg.lock().unwrap();
126        let msg = format!("{}: {}", name, msg);
127        print(&msg);
128    }
129
130    fn print_current_complete_msg(&self) {
131        let msg = self.current_msg.lock().unwrap();
132        let name = self.name.as_str().green();
133        let time = get_formatted_time().green();
134
135        let msg = format!("[{}] {}: {}", time, name, msg);
136        print(&msg);
137    }
138
139    pub fn start_session(self) {
140        terminal::enable_raw_mode().unwrap();
141
142        let (close_tx, close_rx) = std::sync::mpsc::channel::<bool>();
143        let output_clone = self.clone();
144        let input_clone = self.clone();
145
146        let close_tx_clone = close_tx.clone();
147        thread::spawn(move || {
148            output_clone.watch_output();
149            close_tx_clone.send(true).unwrap();
150        });
151
152        thread::spawn(move || {
153            input_clone.watch_input();
154            close_tx.send(true).unwrap();
155        });
156
157        close_rx.recv().unwrap(); // wait for one of them to exit;
158    }
159}
160
161impl Clone for User {
162    fn clone(&self) -> Self {
163        User {
164            current_msg: Arc::clone(&self.current_msg),
165            name: self.name.clone(),
166            stream: self.stream.try_clone().unwrap(),
167        }
168    }
169}
170
171impl Drop for User {
172    fn drop(&mut self) {
173        terminal::disable_raw_mode().unwrap();
174    }
175}