You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

144 lines
4.1 KiB

use clap::Parser;
use std::fs::File;
use std::io::Write;
use std::io::{self, BufRead};
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::thread;
mod chat_error;
use chat_error::ChatError;
mod chat_user;
mod chat_server;
use chat_server::ChatServer;
mod config;
use config::ConfigArgs;
fn handle_client(
mut stream: TcpStream,
server: Arc<Mutex<ChatServer>>,
client_id: &mut Option<u64>,
) -> Result<(), ChatError> {
let mut writer = stream.try_clone()?;
let reader = io::BufReader::new(&mut stream);
let mut lines = reader.lines();
// greet and recv nick
write!(writer, "Hello and welcome to RustChat!\nNick: ")?;
let mut nick;
loop {
nick = lines
.next()
.ok_or(ChatError::Protocol(String::from("Could not recv nickname")))??;
// check for nick name uniqueness
let mut srv = server.lock()?;
if !srv.is_nick_in_use(&nick) {
// print joined msg and register at server
srv.send_to_all(&format!("* {} has joined the chat", nick))?;
let curr_users = srv.get_user_count();
let users_in_room_msg = if curr_users == 1 {
String::from("is 1 user")
} else {
format!("are {} users", curr_users)
};
write!(
writer,
" *** {}\n *** You are known as {}. There {} in the room.\n",
srv.config.greeting_msg, nick, users_in_room_msg
)?;
*client_id = Some(srv.register(nick.clone(), writer));
break;
}
write!(
writer,
"This nickname is already in use, please choose another.\nNick: "
)?;
}
// read lines
for line in lines {
let msg_raw = line?;
if msg_raw.is_empty() {
continue;
}
let msg = format!("<{}> {}", nick, msg_raw);
server.lock()?.send_to_all(&msg)?;
}
Ok(())
}
fn run_thread(stream: TcpStream, server: Arc<Mutex<ChatServer>>) {
let mut client_id = None;
let quit_reason = match handle_client(stream, Arc::clone(&server), &mut client_id) {
Ok(()) => String::from("Connection closed by user's choice"),
Err(err) => format!("Client foobar't their thread: {:?}", err),
};
if let Some(client_id) = client_id {
let mut srv = server.lock().unwrap();
let name = srv
.deregister(client_id)
.map(|client| client.name)
.unwrap_or("<unknown user>".into());
let quit_msg = format!("* {} has left the chat ({})", name, quit_reason);
println!("{}", quit_msg);
srv.send_to_all(&quit_msg).unwrap();
} else {
println!("Client thread terminated before identifying themselves");
}
}
/// Chatserver in RUST!
#[derive(Parser, Debug)]
#[clap(about, version, author)]
struct Args {
/// Path to config file
#[clap(short, long, default_value = "config.yaml")]
config: String,
/// IP address to bind to
#[clap(long)]
host: Option<String>,
/// Port to run the server on
#[clap(short, long)]
port: Option<u16>,
}
fn main() -> std::io::Result<()> {
// argument parsing
let args = Args::parse();
// read config file
let config_reader = File::open(args.config).expect("Could not open config file");
let mut config: ConfigArgs =
serde_yaml::from_reader(&config_reader).expect("Could not parse config file");
if let Some(host) = args.host {
config.host = host;
}
if let Some(port) = args.port {
config.port = port;
}
let binding_host = format!("{}:{}", config.host, config.port);
println!("Binding to {}", binding_host);
let listener = TcpListener::bind(binding_host)?;
let server = Arc::new(Mutex::new(ChatServer::new(config)));
// accept connections and process them serially
for stream in listener.incoming() {
if let Ok(stream) = stream {
let server_clone = Arc::clone(&server);
thread::spawn(move || {
run_thread(stream, server_clone);
});
}
}
Ok(())
}