mirror of
https://github.com/actix/examples
synced 2025-02-02 09:39:03 +01:00
115 lines
3.2 KiB
Rust
115 lines
3.2 KiB
Rust
use std::{io, thread};
|
|
|
|
use futures_util::{SinkExt as _, StreamExt as _};
|
|
use tokio::{net::TcpStream, select, sync::mpsc};
|
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
|
|
|
mod codec;
|
|
|
|
#[actix_web::main]
|
|
async fn main() {
|
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
|
|
|
println!("Running chat client");
|
|
|
|
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
|
|
let mut cmd_rx = UnboundedReceiverStream::new(cmd_rx);
|
|
|
|
// run blocking terminal input reader on separate thread
|
|
let input_thread = thread::spawn(move || loop {
|
|
let mut cmd = String::with_capacity(32);
|
|
|
|
if io::stdin().read_line(&mut cmd).is_err() {
|
|
log::error!("error reading line");
|
|
return;
|
|
}
|
|
|
|
if cmd.trim() == "/exit" {
|
|
println!("exiting input loop");
|
|
return;
|
|
}
|
|
|
|
cmd_tx.send(cmd).unwrap();
|
|
});
|
|
|
|
let io = TcpStream::connect(("127.0.0.1", 12345)).await.unwrap();
|
|
let mut framed = actix_codec::Framed::new(io, codec::ClientChatCodec);
|
|
|
|
loop {
|
|
select! {
|
|
Some(msg) = framed.next() => {
|
|
match msg {
|
|
Ok(codec::ChatResponse::Message(ref msg)) => {
|
|
println!("message: {msg}");
|
|
}
|
|
Ok(codec::ChatResponse::Joined(ref msg)) => {
|
|
println!("!!! joined: {msg}");
|
|
}
|
|
|
|
Ok(codec::ChatResponse::Rooms(rooms)) => {
|
|
println!("!!! Available rooms:");
|
|
for room in rooms {
|
|
println!("{room}");
|
|
}
|
|
}
|
|
|
|
// respond to pings with a "pong"
|
|
Ok(codec::ChatResponse::Ping) => { framed.send(codec::ChatRequest::Ping).await.unwrap(); },
|
|
|
|
_ => { eprintln!("{msg:?}"); }
|
|
}
|
|
}
|
|
|
|
Some(cmd) = cmd_rx.next() => {
|
|
if cmd.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
if cmd == "/exit" {
|
|
println!("exiting recv loop");
|
|
return;
|
|
}
|
|
|
|
if let Some(req) = parse_client_command(&cmd) {
|
|
// submit client command
|
|
framed.send(req).await.unwrap();
|
|
}
|
|
}
|
|
|
|
else => break
|
|
}
|
|
}
|
|
|
|
input_thread.join().unwrap();
|
|
}
|
|
|
|
fn parse_client_command(msg: &str) -> Option<codec::ChatRequest> {
|
|
let m = msg.trim();
|
|
|
|
if m.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
// we check for /sss type of messages
|
|
if m.starts_with('/') {
|
|
let v: Vec<&str> = m.splitn(2, ' ').collect();
|
|
match v[0] {
|
|
"/list" => Some(codec::ChatRequest::List),
|
|
"/join" => {
|
|
if v.len() == 2 {
|
|
Some(codec::ChatRequest::Join(v[1].to_owned()))
|
|
} else {
|
|
println!("!!! room name is required");
|
|
None
|
|
}
|
|
}
|
|
_ => {
|
|
println!("!!! unknown command");
|
|
None
|
|
}
|
|
}
|
|
} else {
|
|
Some(codec::ChatRequest::Message(m.to_owned()))
|
|
}
|
|
}
|