better support messaging and parsing on client and server
This commit is contained in:
parent
d5935cdfff
commit
d05df7979f
@ -145,14 +145,12 @@ fn help() {
|
|||||||
println!(
|
println!(
|
||||||
"/leave [room-name] <- Leave the given room. Error if the you are not already in the room"
|
"/leave [room-name] <- Leave the given room. Error if the you are not already in the room"
|
||||||
);
|
);
|
||||||
println!("/show [room-name] <- Switch your focus to the given room. It is suggested to join the room first");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start() {
|
pub fn start() {
|
||||||
clear();
|
clear();
|
||||||
println!("Starting the IRC client. No spaces allowed in nicknames or room names. /help to see available commands");
|
println!("Starting the IRC client. No spaces allowed in nicknames or room names. /help to see available commands");
|
||||||
let mut nick: String;
|
let mut nick: String;
|
||||||
let mut active_room: String = String::new();
|
|
||||||
loop {
|
loop {
|
||||||
nick = input!("Enter your nickname : ");
|
nick = input!("Enter your nickname : ");
|
||||||
if nick.contains(" ") {
|
if nick.contains(" ") {
|
||||||
@ -200,64 +198,47 @@ pub fn start() {
|
|||||||
one_param_op(codes::client::REGISTER_NICK, &mut stream, &nick);
|
one_param_op(codes::client::REGISTER_NICK, &mut stream, &nick);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let inp;
|
let inp = input!("");
|
||||||
if active_room.is_empty() {
|
|
||||||
inp = input!("");
|
|
||||||
} else {
|
|
||||||
inp = input!("\n[{}]:", active_room);
|
|
||||||
}
|
|
||||||
let mut args: std::str::SplitWhitespace<'_> = inp.split_whitespace();
|
|
||||||
let command: Option<&str> = args.next();
|
|
||||||
|
|
||||||
match command {
|
match inp.split_once(" ") {
|
||||||
Some(cmd) => {
|
Some((cmd, param)) => match cmd {
|
||||||
let param: Option<&str> = args.next();
|
|
||||||
match cmd {
|
|
||||||
"/quit" => {
|
"/quit" => {
|
||||||
disconnect(&mut stream);
|
disconnect(&mut stream);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
"/rooms" => no_param_op(codes::client::LIST_ROOMS, &mut stream),
|
"/rooms" => no_param_op(codes::client::LIST_ROOMS, &mut stream),
|
||||||
"/users" => no_param_op(codes::client::LIST_USERS, &mut stream),
|
"/users" => no_param_op(codes::client::LIST_USERS, &mut stream),
|
||||||
"/list" => match param {
|
"/list" => match param.split_once(" ") {
|
||||||
Some(room) => {
|
Some((room, _)) => {
|
||||||
one_param_op(codes::client::LIST_USERS_IN_ROOM, &mut stream, room);
|
one_param_op(codes::client::LIST_USERS_IN_ROOM, &mut stream, room);
|
||||||
}
|
}
|
||||||
None => {
|
_ => {
|
||||||
println!("Room name expected, but not provided");
|
println!("Malformaed. Try /list [room-name]");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/join" => match param {
|
"/join" => match param.split_once(" ") {
|
||||||
Some(room) => {
|
Some((_, _)) => {
|
||||||
one_param_op(codes::client::JOIN_ROOM, &mut stream, room);
|
println!("Malformed. Try /join [room-name]");
|
||||||
}
|
}
|
||||||
None => {
|
_ => {
|
||||||
println!("Room name expected, but not provided");
|
one_param_op(codes::client::JOIN_ROOM, &mut stream, param);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/show" => match param {
|
|
||||||
Some(room) => {
|
"/leave" => match param.split_once(" ") {
|
||||||
clear();
|
Some((_, _)) => {
|
||||||
active_room = room.to_string();
|
println!("Malformed. Try /leave [room-name]");
|
||||||
}
|
}
|
||||||
None => {
|
_ => {
|
||||||
println!("Room name expected, but not provided")
|
one_param_op(codes::client::LEAVE_ROOM, &mut stream, param);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/leave" => match param {
|
"/msg" => match param.split_once(" ") {
|
||||||
Some(room) => {
|
|
||||||
one_param_op(codes::client::LEAVE_ROOM, &mut stream, room);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
println!("Room name expected, but not provided");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/msg" => match inp.split_once(" ") {
|
|
||||||
Some((room, msg)) => {
|
Some((room, msg)) => {
|
||||||
two_param_op(codes::client::MESSAGE_ROOM, &mut stream, room, msg);
|
two_param_op(codes::client::MESSAGE_ROOM, &mut stream, room, msg);
|
||||||
}
|
}
|
||||||
None => {
|
_ => {
|
||||||
println!("Usage: /msg [room] [message]");
|
println!("Ufsage: /msg [room] [message]");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/help" => {
|
"/help" => {
|
||||||
@ -267,23 +248,14 @@ pub fn start() {
|
|||||||
println!("Invalid command");
|
println!("Invalid command");
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if active_room.is_empty() {
|
one_param_op(codes::client::MESSAGE, &mut stream, &inp);
|
||||||
println!("use '/show [room]' to set an active room before sending a message");
|
|
||||||
} else {
|
|
||||||
let message: String = inp;
|
|
||||||
two_param_op(
|
|
||||||
codes::client::MESSAGE_ROOM,
|
|
||||||
&mut stream,
|
|
||||||
&active_room,
|
|
||||||
&message,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
one_param_op(codes::client::MESSAGE, &mut stream, &inp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
println!("Failed to connect to {} with nickname {} ", host, nick);
|
println!("Failed to connect to {} with nickname {} ", host, nick);
|
||||||
}
|
}
|
||||||
|
@ -33,3 +33,5 @@ pub mod codes {
|
|||||||
pub fn clear() {
|
pub fn clear() {
|
||||||
print!("\x1B[2J");
|
print!("\x1B[2J");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const SPACE_BYTES: &[u8] = &[0x20];
|
||||||
|
108
src/server.rs
108
src/server.rs
@ -8,7 +8,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use prompted::input;
|
use prompted::input;
|
||||||
use rust_irc::{clear, codes};
|
use rust_irc::{clear, codes, SPACE_BYTES};
|
||||||
|
|
||||||
const SERVER_ADDRESS: &str = "0.0.0.0:6667";
|
const SERVER_ADDRESS: &str = "0.0.0.0:6667";
|
||||||
const MAX_USERS: usize = 20;
|
const MAX_USERS: usize = 20;
|
||||||
@ -28,34 +28,21 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn message(room: &str, msg: &str, sender: &str, server: &Arc<Mutex<Server>>) {
|
fn message_room(room: &str, msg: &str, sender: &str, server: &Arc<Mutex<Server>>) {
|
||||||
let size = room.len() + msg.len() + sender.len() + 3;
|
let code_bytes = &[codes::client::MESSAGE_ROOM];
|
||||||
let mut out_buf: Vec<u8> = vec![0; size];
|
let room_bytes = room.as_bytes();
|
||||||
|
let msg_bytes = msg.as_bytes();
|
||||||
let mut byte: usize = 0;
|
let sender_bytes = sender.as_bytes();
|
||||||
out_buf[byte] = codes::client::MESSAGE_ROOM;
|
let out_buf: &Vec<u8> = &[
|
||||||
byte += 1;
|
code_bytes,
|
||||||
|
room_bytes,
|
||||||
for i in 0..room.len() {
|
SPACE_BYTES,
|
||||||
out_buf[byte] = *room.as_bytes().get(i).unwrap();
|
sender_bytes,
|
||||||
byte += 1;
|
SPACE_BYTES,
|
||||||
}
|
msg_bytes,
|
||||||
|
]
|
||||||
out_buf[byte] = 0x20;
|
.concat();
|
||||||
byte += 1;
|
println!("out buf {:?} ", out_buf.to_ascii_lowercase());
|
||||||
|
|
||||||
for i in 0..sender.len() {
|
|
||||||
out_buf[byte] = *sender.as_bytes().get(i).unwrap();
|
|
||||||
byte += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
out_buf[byte] = 0x20;
|
|
||||||
byte += 1;
|
|
||||||
|
|
||||||
for i in 0..msg.len() {
|
|
||||||
out_buf[byte] = *msg.as_bytes().get(i).unwrap();
|
|
||||||
byte += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut guard: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
let mut guard: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
||||||
let server: &mut Server = guard.deref_mut();
|
let server: &mut Server = guard.deref_mut();
|
||||||
@ -64,6 +51,7 @@ fn message(room: &str, msg: &str, sender: &str, server: &Arc<Mutex<Server>>) {
|
|||||||
//2: Make sure sender is a member of the room, ifn error
|
//2: Make sure sender is a member of the room, ifn error
|
||||||
//3: Message all non-sender users in the room the message, ifnone error empty room
|
//3: Message all non-sender users in the room the message, ifnone error empty room
|
||||||
//4: Message the sender RESPONSE_OK
|
//4: Message the sender RESPONSE_OK
|
||||||
|
println!("checking room {} ", room);
|
||||||
let room_users: Option<&Vec<String>> = server.rooms.get(room);
|
let room_users: Option<&Vec<String>> = server.rooms.get(room);
|
||||||
let mut sender_stream = server
|
let mut sender_stream = server
|
||||||
.users
|
.users
|
||||||
@ -213,9 +201,11 @@ fn handle_client(
|
|||||||
leave_room(server, &nickname, room, stream);
|
leave_room(server, &nickname, room, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Generic message sent to all users of all rooms the clients nickname is in, except the client nickname
|
||||||
codes::client::MESSAGE => {
|
codes::client::MESSAGE => {
|
||||||
#[cfg(debug_assertions)]
|
let p: String = String::from_utf8_lossy(param_bytes).to_string();
|
||||||
println!("MESSAGE");
|
|
||||||
|
message_all_senders_rooms(server, &nickname, &p, stream);
|
||||||
stream.write_all(&[codes::RESPONSE_OK]).unwrap();
|
stream.write_all(&[codes::RESPONSE_OK]).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,13 +213,13 @@ fn handle_client(
|
|||||||
stream.write_all(&[codes::RESPONSE_OK]).unwrap();
|
stream.write_all(&[codes::RESPONSE_OK]).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//A message sent just to the users of the room passed in, except the client nickname
|
||||||
codes::client::MESSAGE_ROOM => {
|
codes::client::MESSAGE_ROOM => {
|
||||||
let p: String = String::from_utf8_lossy(param_bytes).to_string();
|
let p: String = String::from_utf8_lossy(param_bytes).to_string();
|
||||||
let params: Option<(&str, &str)> = p.split_once(" ");
|
let params: Option<(&str, &str)> = p.split_once(" ");
|
||||||
|
|
||||||
match params {
|
match params {
|
||||||
Some((room, msg)) => {
|
Some((room, msg)) => {
|
||||||
message(room, msg, nickname, server);
|
message_room(room, msg, nickname, server);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
stream
|
stream
|
||||||
@ -250,6 +240,42 @@ fn handle_client(
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn message_all_senders_rooms(
|
||||||
|
server: &Arc<Mutex<Server>>,
|
||||||
|
sender: &str,
|
||||||
|
message: &str,
|
||||||
|
stream: &mut TcpStream,
|
||||||
|
) {
|
||||||
|
let rooms = get_rooms_of_user(server, sender);
|
||||||
|
let mut guard = server.lock().unwrap();
|
||||||
|
let sender_bytes: &[u8] = sender.as_bytes();
|
||||||
|
let code_bytes: &[u8] = &[codes::client::MESSAGE_ROOM];
|
||||||
|
let message_bytes: &[u8] = message.as_bytes();
|
||||||
|
let space_bytes: &[u8] = &[0x20];
|
||||||
|
for room in rooms {
|
||||||
|
let room_bytes: &[u8] = room.as_bytes();
|
||||||
|
let users = guard.rooms.get(&room).unwrap().clone();
|
||||||
|
let out_buf: &Vec<u8> = &[
|
||||||
|
code_bytes,
|
||||||
|
room_bytes,
|
||||||
|
space_bytes,
|
||||||
|
sender_bytes,
|
||||||
|
space_bytes,
|
||||||
|
message_bytes,
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
|
||||||
|
for user in users {
|
||||||
|
if !user.eq(sender) {
|
||||||
|
let stream = guard.users.get_mut(&user);
|
||||||
|
stream.unwrap().write_all(out_buf).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.write_all(&[codes::RESPONSE_OK]).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a user from any rooms they may be in, then drop the user
|
||||||
fn remove_user(server: &Arc<Mutex<Server>>, nickname: &str, stream: &mut TcpStream) {
|
fn remove_user(server: &Arc<Mutex<Server>>, nickname: &str, stream: &mut TcpStream) {
|
||||||
let mut guard: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
let mut guard: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
||||||
let server: &mut Server = guard.deref_mut();
|
let server: &mut Server = guard.deref_mut();
|
||||||
@ -261,8 +287,8 @@ fn remove_user(server: &Arc<Mutex<Server>>, nickname: &str, stream: &mut TcpStre
|
|||||||
users.remove(nickname);
|
users.remove(nickname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a nickname to the Server, being careful to handle a possible collision.
|
||||||
fn register_nick(server: &Arc<Mutex<Server>>, nickname: &str, stream: &mut TcpStream) {
|
fn register_nick(server: &Arc<Mutex<Server>>, nickname: &str, stream: &mut TcpStream) {
|
||||||
// Check for nickname collision
|
|
||||||
let mut unlocked_server: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
let mut unlocked_server: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
||||||
if unlocked_server.users.contains_key(nickname) {
|
if unlocked_server.users.contains_key(nickname) {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
@ -271,18 +297,17 @@ fn register_nick(server: &Arc<Mutex<Server>>, nickname: &str, stream: &mut TcpSt
|
|||||||
.write_all(&[codes::ERROR, codes::error::NICKNAME_COLLISION])
|
.write_all(&[codes::ERROR, codes::error::NICKNAME_COLLISION])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
// Add the user to the user list
|
|
||||||
let clone: TcpStream = stream.try_clone().expect("fail to clone");
|
let clone: TcpStream = stream.try_clone().expect("fail to clone");
|
||||||
let addr: String = clone.peer_addr().unwrap().to_string();
|
let addr: String = clone.peer_addr().unwrap().to_string();
|
||||||
|
|
||||||
unlocked_server.users.insert(nickname.to_string(), clone);
|
unlocked_server.users.insert(nickname.to_string(), clone);
|
||||||
// Send response ok
|
|
||||||
stream.write_all(&[codes::RESPONSE_OK]).unwrap();
|
stream.write_all(&[codes::RESPONSE_OK]).unwrap();
|
||||||
|
|
||||||
println!("{} has registered nickname {}", addr, nickname);
|
println!("{} has registered nickname {}", addr, nickname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add user to a room, creating the room if necessary
|
||||||
|
/// Provide feedback about what room was just joined, and which rooms the user may be in
|
||||||
fn join_room(server: &Arc<Mutex<Server>>, user: &str, room: &str, stream: &mut TcpStream) {
|
fn join_room(server: &Arc<Mutex<Server>>, user: &str, room: &str, stream: &mut TcpStream) {
|
||||||
let mut unlocked_server: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
let mut unlocked_server: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
||||||
|
|
||||||
@ -313,6 +338,8 @@ fn join_room(server: &Arc<Mutex<Server>>, user: &str, room: &str, stream: &mut T
|
|||||||
stream.write_all(out).unwrap();
|
stream.write_all(out).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove a user from a room, handling possible error cases.
|
||||||
|
/// Provide feedback about what room was just left, and which rooms the user may still be in
|
||||||
fn leave_room(server: &Arc<Mutex<Server>>, user: &str, room: &str, stream: &mut TcpStream) {
|
fn leave_room(server: &Arc<Mutex<Server>>, user: &str, room: &str, stream: &mut TcpStream) {
|
||||||
let mut unlocked_server: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
let mut unlocked_server: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
||||||
match unlocked_server.rooms.get_mut(room) {
|
match unlocked_server.rooms.get_mut(room) {
|
||||||
@ -352,8 +379,8 @@ fn leave_room(server: &Arc<Mutex<Server>>, user: &str, room: &str, stream: &mut
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate on all rooms, capture each room name which has the user
|
/// Iterate on all rooms, capture each room name which has the user
|
||||||
// return a vec of strings of room names
|
/// return a vec of strings of room names
|
||||||
fn get_rooms_of_user(server: &Arc<Mutex<Server>>, user: &str) -> Vec<String> {
|
fn get_rooms_of_user(server: &Arc<Mutex<Server>>, user: &str) -> Vec<String> {
|
||||||
let mut result: Vec<String> = vec![];
|
let mut result: Vec<String> = vec![];
|
||||||
let guard: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
let guard: std::sync::MutexGuard<'_, Server> = server.lock().unwrap();
|
||||||
@ -452,13 +479,14 @@ pub fn start() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Main Menu Loop on the main thread.
|
||||||
loop {
|
loop {
|
||||||
println!("0: Quit Server");
|
println!("0: Quit Server");
|
||||||
println!("1: list connected users");
|
println!("1: list connected users");
|
||||||
println!("2: list rooms");
|
println!("2: list rooms");
|
||||||
println!("3: Broadcast message to all");
|
println!("3: Broadcast message to all");
|
||||||
println!("4: Freeze server via double lock (for testing)");
|
println!("4: Freeze server via double lock (for testing)");
|
||||||
let inp: String = input!(":");
|
let inp: String = input!("");
|
||||||
match inp.parse::<u8>() {
|
match inp.parse::<u8>() {
|
||||||
Ok(num) => match num {
|
Ok(num) => match num {
|
||||||
0 => {
|
0 => {
|
||||||
|
Reference in New Issue
Block a user