implemented notifications and messages

This commit is contained in:
mb 2022-08-22 15:54:36 +03:00
parent 28a3812812
commit 695ac6daa2
12 changed files with 1023 additions and 315 deletions

View file

@ -0,0 +1,166 @@
use harsh_common::ClientRequest;
pub enum Command {
Help,
Request(ClientRequest),
}
pub fn parse(input: &str) -> Option<Command> {
let mut parts = smart_split(input).into_iter();
let command = match parts.next()?.as_str() {
"help" => return Some(Command::Help),
"ping" => {
let rest = parts.collect::<Box<[_]>>();
let content = rest.join(" ");
ClientRequest::new_ping(content)
}
"chanls" => ClientRequest::new_channel_list(),
"chanadd" => {
let name = parts.next()?;
ClientRequest::new_channel_create(name)
}
"chandel" => {
let id = parts.next()?.parse().ok()?;
ClientRequest::new_channel_delete(id)
}
"changname" => {
let id = parts.next()?.parse().ok()?;
ClientRequest::new_channel_get_name(id)
}
"chansname" => {
let id = parts.next()?.parse().ok()?;
let name = parts.next()?;
ClientRequest::new_channel_set_name(id, name)
}
"msgls" => {
let channel_id = parts.next()?.parse().ok()?;
ClientRequest::new_message_list(channel_id)
}
"msgadd" => {
let channel_id = parts.next()?.parse().ok()?;
let content = parts.next()?;
ClientRequest::new_message_create(channel_id, content)
}
"msgdel" => {
let channel_id = parts.next()?.parse().ok()?;
let id = parts.next()?.parse().ok()?;
ClientRequest::new_message_delete(channel_id, id)
}
"msggcont" => {
let channel_id = parts.next()?.parse().ok()?;
let id = parts.next()?.parse().ok()?;
ClientRequest::new_message_get_content(channel_id, id)
}
"msgscont" => {
let channel_id = parts.next()?.parse().ok()?;
let id = parts.next()?.parse().ok()?;
let content = parts.next()?;
ClientRequest::new_message_set_content(channel_id, id, content)
}
_ => return None,
};
Some(Command::Request(command))
}
pub const CMDS: &'static [Description] = &[
// all commands
Description::new("help", &[], "returns a help message"),
Description::new(
"ping",
&["content"],
"sends a ping with the specified content",
),
Description::new("chanls", &[], "list channels"),
Description::new("chanadd", &["name"], "creates a new channel"),
Description::new("chandel", &["id"], "delete a channel by its id"),
Description::new("changname", &["id"], "get a channel's name"),
Description::new("chansname", &["id", "name"], "set a channel's name"),
Description::new("msgls", &["channel_id"], "list messages"),
Description::new("msgadd", &["channel_id", "content"], "create a message"),
Description::new("msgdel", &["channel_id", "id"], "delete a message"),
Description::new("msggcont", &["channel_id", "id"], "get a message's content"),
Description::new(
"msgscont",
&["channel_id", "id", "content"],
"set a message's content",
),
];
pub fn smart_split(input: &str) -> Vec<String> {
let input = input.trim();
let mut result = Vec::new();
let mut capturing = false;
let mut ignoring = false;
let mut current = String::new();
for char in input.chars() {
let char: char = char;
if ignoring {
current.push(char);
ignoring = false;
continue;
}
match char {
'\\' => {
ignoring = true;
}
'"' => {
capturing = !capturing;
}
' ' if !capturing => {
result.push(current);
current = String::new();
}
_ => current.push(char),
}
}
result.push(current);
result
}
#[test]
fn test_smart_split() {
assert_eq!(
smart_split("hello world"),
vec!["hello".to_string(), "world".to_string()]
);
assert_eq!(
smart_split(r#""lorem ipsum" "dolor amit""#),
vec!["lorem ipsum".to_string(), "dolor amit".to_string()]
);
assert_eq!(
smart_split(r#"lorem "ipsum do"lor "amit""#),
vec![
"lorem".to_string(),
"ipsum dolor".to_string(),
"amit".to_string()
]
);
}
pub struct Description {
name: &'static str,
params: &'static [&'static str],
desc: &'static str,
}
impl Description {
pub const fn new(
name: &'static str,
params: &'static [&'static str],
desc: &'static str,
) -> Self {
Self { name, desc, params }
}
}
pub fn help() {
for &Description { name, params, desc } in CMDS {
let mut usage = params.iter().map(|s| s.to_string()).collect::<Vec<_>>();
usage.insert(0, name.to_string());
let usage = usage.join(" ");
println!("{name}:\n\tusage:\t\t{usage}\n\tdescription:\t{desc}");
}
}

View file

@ -1,25 +1,38 @@
use std::time::Duration;
use std::{
io::{stdout, Write},
process::exit,
};
use harsh_common::ServerRequest;
use tokio::{
io::{stdin, AsyncBufReadExt, AsyncWriteExt, BufReader},
net::TcpStream,
time,
};
const ADDRESS: &'static str = "localhost:42069";
#[tokio::main]
async fn main() {
println!("starting client ...");
let stream = TcpStream::connect("localhost:8080").await.unwrap();
println!("connected to 'localhost:8080'");
println!("[main/info] starting client ...");
let stream = TcpStream::connect(ADDRESS).await.unwrap();
println!("[main/info] connected to '{ADDRESS}'");
let (reader, writer) = stream.into_split();
tokio::spawn(async {
let mut reader = BufReader::new(reader);
loop {
let mut line = String::new();
reader.read_line(&mut line).await.unwrap();
println!("received '{line}'");
time::sleep(Duration::from_millis(100)).await;
match reader.read_line(&mut line).await {
Ok(0) => {
break;
}
_ => (),
}
if let Some(parsed) = ServerRequest::try_parse(&line) {
println!("[main/info] received '{parsed:?}'");
}
}
println!("[main/info] connection closed, goodbye.");
exit(0);
});
let input_loop = tokio::spawn(async {
@ -27,14 +40,16 @@ async fn main() {
let mut writer = writer;
loop {
print!("$> ");
stdout().lock().flush().unwrap();
let mut line = String::new();
input.read_line(&mut line).await.unwrap();
let input = commands::parse(&line);
match input {
None => println!("failed to parse command"),
None => println!("[main/warn] failed to parse command"),
Some(commands::Command::Help) => commands::help(),
Some(commands::Command::Request(cmd)) => {
println!("sending..");
println!("[main/info] sending..");
writer.write_all(cmd.serialize().as_bytes()).await.unwrap();
writer.write_all(b"\n").await.unwrap();
}
@ -42,135 +57,8 @@ async fn main() {
}
});
println!("awaiting input ...");
println!("[main/info] awaiting input ...");
input_loop.await.unwrap();
}
mod commands {
use harsh_common::ClientRequest;
pub enum Command {
Help,
Request(ClientRequest),
}
pub fn parse(input: &str) -> Option<Command> {
let mut parts = smart_split(input).into_iter();
let command = match parts.next()?.as_str() {
"help" => return Some(Command::Help),
"ping" => {
let rest = parts.collect::<Box<[_]>>();
let content = rest.join(" ");
ClientRequest::new_ping(content)
}
"chanls" => ClientRequest::new_channel_list(),
"chanadd" => {
let name = parts.next()?;
ClientRequest::new_channel_create(name)
}
"chandel" => {
let id = parts.next()?.parse().ok()?;
ClientRequest::new_channel_delete(id)
}
"changetname" => {
let id = parts.next()?.parse().ok()?;
ClientRequest::new_channel_get_name(id)
}
_ => return None,
};
Some(Command::Request(command))
}
pub const CMDS: &'static [Description] = &[
// all commands
Description::new("help", &[], "returns a help message"),
Description::new(
"ping",
&["content"],
"sends a ping with the specified content",
),
Description::new("chanls", &[], "list channels"),
Description::new("chanadd", &["name"], "creates a new channel"),
Description::new("chandel", &["id"], "delete a channel by its id"),
Description::new("changetname", &["id"], "get a channel's name"),
];
pub fn smart_split(input: &str) -> Vec<String> {
let input = input.trim();
let mut result = Vec::new();
let mut capturing = false;
let mut ignoring = false;
let mut current = String::new();
for char in input.chars() {
let char: char = char;
if ignoring {
current.push(char);
ignoring = false;
continue;
}
match char {
'\\' => {
ignoring = true;
}
'"' => {
capturing = !capturing;
}
' ' if !capturing => {
result.push(current);
current = String::new();
}
_ => current.push(char),
}
}
result.push(current);
result
}
#[test]
fn test_smart_split() {
assert_eq!(
smart_split("hello world"),
vec!["hello".to_string(), "world".to_string()]
);
assert_eq!(
smart_split(r#""lorem ipsum" "dolor amit""#),
vec!["lorem ipsum".to_string(), "dolor amit".to_string()]
);
assert_eq!(
smart_split(r#"lorem "ipsum do"lor "amit""#),
vec![
"lorem".to_string(),
"ipsum dolor".to_string(),
"amit".to_string()
]
);
}
pub struct Description {
name: &'static str,
params: &'static [&'static str],
desc: &'static str,
}
impl Description {
pub const fn new(
name: &'static str,
params: &'static [&'static str],
desc: &'static str,
) -> Self {
Self { name, desc, params }
}
}
pub fn help() {
for &Description { name, params, desc } in CMDS {
let mut usage = params.iter().map(|s| s.to_string()).collect::<Vec<_>>();
usage.insert(0, name.to_string());
let usage = usage.join(" ");
println!("{name}:\n\tusage:\n\t\t{usage}\n\n\tdescription:\n\t\t{desc}\n");
}
}
}
mod commands;