From 8dc166fad4b68c06a1454227e9fcce0ca1ea0e3a Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Sun, 24 Dec 2023 18:21:19 +0100 Subject: [PATCH] feat: add method for bidirectional feedback handling --- README.md | 33 ++++++++++--- examples/arsnova-client-tui.rs | 24 ++++----- src/client.rs | 89 +++++++++++++++++++++++++--------- 3 files changed, 104 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 8ca1826..3b9a45d 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,16 @@ let room_info = client.get_room_info("12345678").await.expect("room information" You can get feedback information in two different ways: Direct request and getting notified about changes. +#### Send feedback + +Register a channel receiver and send incoming feedback using the client. + +```rust +let (fb_tx, fb_rx) = channel::(10); + +let _ = client.register_feedback_receiver(&cli.room, fb_rx).await; +``` + #### Direct request You can request (poll) the current feedback: @@ -46,19 +56,30 @@ let _ = client.on_feedback_changed(&cli.room, FeedbackHandler::Fn(|feedback| {/* Forward feedback to a channel: ```rust -let (tx, rx) = tokio::sync::mpsc::channel::(10); +let (in_tx, in_rx) = tokio::sync::mpsc::channel::(10); -let _ = client.on_feedback_changed(&cli.room, FeedbackHandler::Sender(tx.clone())).await; +let _ = client.on_feedback_changed(&cli.room, FeedbackHandler::Sender(in_tx)).await; ``` -#### Send feedback +#### Both: Send and receive Feedback updates -Register a channel receiver and send incoming feedback using the client. +Handle remote feedback changes and feedback updates to be sent: + +```mermaid +flowchart LR + UI["`**UI**`"] --> A[out_tx] + A[out_tx] -->|FeedbackValue| B[out_rx] + B --> W["`*WebSocket connection*`"] + D[in_tx] --> |Feedback| C[in_rx] + W --> D + C --> UI +``` ```rust -let (fb_tx, fb_rx) = channel::(10); +let (in_tx, inrx) = channel::(10); // Incoming from remote +let (out_tx, out_rx) = channel::(10); // Outgoing to remote -let _ = client.register_feedback_receiver(&cli.room, fb_rx).await; +let _ = client.on_feedback_changed(&cli.room, FeedbackHandler::SenderReceiver(in_tx, out_rx)).await; ``` ## Example diff --git a/examples/arsnova-client-tui.rs b/examples/arsnova-client-tui.rs index 212b0cb..432c2a5 100644 --- a/examples/arsnova-client-tui.rs +++ b/examples/arsnova-client-tui.rs @@ -62,11 +62,10 @@ async fn main() -> Result<(), ()> { let client = client.guest_login().await.map_err(|_| ())?; - let (tx, rx) = channel::(10); + let (in_tx, in_rx) = channel::(10); + let (out_tx, out_rx) = channel::(10); - let (fb_tx, fb_rx) = channel::(10); - - let _ = tx + let _ = in_tx .clone() .send(client.get_feedback(&cli.room).await.unwrap()) .await; @@ -76,12 +75,12 @@ async fn main() -> Result<(), ()> { let mut terminal = Terminal::new(CrosstermBackend::new(stdout())).map_err(|_| ())?; terminal.clear().map_err(|_| ())?; - let l1 = client.on_feedback_changed(&cli.room, FeedbackHandler::Sender(tx.clone())); + let l1 = client.on_feedback_changed(&cli.room, FeedbackHandler::SenderReceiver(in_tx, out_rx)); let room_info = client.get_room_info(&cli.room).await.map_err(|_| ())?; let title = format!("Live Feedback: {} ({})", room_info.name, room_info.short_id); - let l2 = create_ui(&mut terminal, &title, rx); + let l2 = create_ui(&mut terminal, &title, in_rx); let l3 = tokio::spawn(async move { loop { @@ -94,16 +93,16 @@ async fn main() -> Result<(), ()> { match key.code { KeyCode::Esc => break, KeyCode::Char('a') | KeyCode::Char('1') => { - let _ = fb_tx.send(FeedbackValue::VeryGood).await; + let _ = out_tx.send(FeedbackValue::VeryGood).await; } KeyCode::Char('b') | KeyCode::Char('2') => { - let _ = fb_tx.send(FeedbackValue::Good).await; + let _ = out_tx.send(FeedbackValue::Good).await; } KeyCode::Char('c') | KeyCode::Char('3') => { - let _ = fb_tx.send(FeedbackValue::Bad).await; + let _ = out_tx.send(FeedbackValue::Bad).await; } KeyCode::Char('d') | KeyCode::Char('4') => { - let _ = fb_tx.send(FeedbackValue::VeryBad).await; + let _ = out_tx.send(FeedbackValue::VeryBad).await; } _ => {} }; @@ -113,13 +112,10 @@ async fn main() -> Result<(), ()> { } }); - let l4 = client.register_feedback_receiver(&cli.room, fb_rx); - select! { _ = l1 => {}, _ = l2 => {}, - _ = l3 => {}, - _ = l4 => {} + _ = l3 => {} } let _ = stdout().execute(LeaveAlternateScreen).map_err(|_| ()); diff --git a/src/client.rs b/src/client.rs index 8c76eee..e3f88e6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -207,8 +207,12 @@ impl Feedback { #[allow(dead_code)] pub enum FeedbackHandler { + /// Handle incoming `Feedback` using a fn Fn(fn(&Feedback)), + /// Handle incoming `Feedback` by sending it to a `Sender` Sender(Sender), + /// Bidirectional handler for incoming `Feedback` and outgoing `FeedbackValue` + SenderReceiver(Sender, Receiver), } /// A possible feedback value @@ -508,32 +512,51 @@ impl Client { )) .await { - Ok(_) => loop { - select! { - Some(next) = read.next() => { - match &next { - Ok(msg) => { - if msg.is_text() && msg.clone().into_text().unwrap().starts_with("MESSAGE") { - if let Ok(msg) = WsFeedbackMessage::parse(msg.to_text().unwrap()) { - if msg.body.body_type == "FeedbackChanged" { - let feedback = msg.body.payload.get_feedback(); - match &handler { - FeedbackHandler::Fn(f) => f(&feedback), - FeedbackHandler::Sender(tx) => { - let _ = tx.send(feedback).await; - } - }; - } - } - } + Ok(_) => match handler { + FeedbackHandler::Fn(f) => loop { + select! { + Some(next) = read.next() => { + match &next { + Ok(msg) => self.handle_incoming_feedback_with_fn(msg, &f).await, + Err(_) => break } - Err(_) => break + } + _ = tokio::time::sleep(Duration::from_secs(15)) => { + let _ = write.send(Message::Text("\n".to_string())).await; } } - _ = tokio::time::sleep(Duration::from_secs(15)) => { - let _ = write.send(Message::Text("\n".to_string())).await; + }, + FeedbackHandler::Sender(tx) => loop { + select! { + Some(next) = read.next() => { + match &next { + Ok(msg) => self.handle_incoming_feedback_with_sender(msg, &tx).await, + Err(_) => break + } + } + _ = tokio::time::sleep(Duration::from_secs(15)) => { + let _ = write.send(Message::Text("\n".to_string())).await; + } } - } + }, + FeedbackHandler::SenderReceiver(tx, mut rx) => loop { + select! { + Some(next) = read.next() => { + match &next { + Ok(msg) => self.handle_incoming_feedback_with_sender(msg, &tx).await, + Err(_) => break + } + } + Some(value) = rx.recv() => { + let user_id = self.get_user_id().unwrap_or_default(); + let msg = WsCreateFeedbackMessage::new(&room_info.id, &user_id, value.to_owned()).to_string(); + let _ = write.send(Message::Text(msg)).await; + } + _ = tokio::time::sleep(Duration::from_secs(15)) => { + let _ = write.send(Message::Text("\n".to_string())).await; + } + } + }, }, Err(_) => return Err(ConnectionError), } @@ -541,4 +564,26 @@ impl Client { Err(ConnectionError) } + + async fn handle_incoming_feedback_with_fn(&self, msg: &Message, f: &fn(&Feedback)) { + if msg.is_text() && msg.clone().into_text().unwrap().starts_with("MESSAGE") { + if let Ok(msg) = WsFeedbackMessage::parse(msg.to_text().unwrap()) { + if msg.body.body_type == "FeedbackChanged" { + let feedback = msg.body.payload.get_feedback(); + f(&feedback); + } + } + } + } + + async fn handle_incoming_feedback_with_sender(&self, msg: &Message, tx: &Sender) { + if msg.is_text() && msg.clone().into_text().unwrap().starts_with("MESSAGE") { + if let Ok(msg) = WsFeedbackMessage::parse(msg.to_text().unwrap()) { + if msg.body.body_type == "FeedbackChanged" { + let feedback = msg.body.payload.get_feedback(); + let _ = tx.send(feedback).await; + } + } + } + } }