use futures::prelude::*; use smol::channel; use smol::lock::{Mutex, RwLock, OnceCell}; use smol::net::TcpStream; use std::collections::HashMap; use std::sync::Arc; use async_tungstenite::tungstenite::Error; use async_tungstenite::{async_tls::client_async_tls, tungstenite::protocol::Message}; use tinyjson::JsonValue; use crate::JsonObject; #[derive(Debug, Clone)] struct Incoming { nonce: Option, value: JsonValue, } struct NonceInfo { queue: Vec, listener: Option, } #[derive(Debug, Clone)] enum Credential { Empty, Token(String), User{username: String, password: String}, } /// Central connection controller struct Controller { nonce: std::sync::atomic::AtomicU32, sender: channel::Sender, nonce_data: Mutex>, credential: RwLock, } /// Shareable Handle to a Horde connection #[derive(Clone)] pub struct Horde { controller: Arc, } pub struct Request<'a> { nonce: u32, horde: &'a Horde, action: &'a str, body: JsonObject, } enum RequestListener { Value(channel::Sender), Stream(channel::Sender), } impl RequestListener { // Creates a Value variant, returning the receiver side fn value() -> (Self, channel::Receiver) { let (sender, receiver) = channel::unbounded(); (RequestListener::Value(sender), receiver) } // Creates a Stream variant, returning the receiver side fn stream() -> (Self, channel::Receiver) { let (sender, receiver) = channel::unbounded(); (RequestListener::Stream(sender), receiver) } // Sends data to the listener. In case of a value listener, also takes it from the option. // Returns whether the option had a stream to feed. async fn feed(opt_self: &mut Option, value: JsonValue) { match opt_self { Some(RequestListener::Stream(sender)) => { sender.send(value).await.unwrap(); } Some(RequestListener::Value(sender)) => { sender.send(value).await.unwrap(); //*opt = None; } None => panic!("Cannot feed to None"), } } } type SinkBox = Box + Unpin + Send>; enum ConnectionWriterState { Active(SinkBox), Inactive(Arc>), } struct ConnectionWriter(Mutex); impl ConnectionWriter { async fn send (&self, msg: Message) { let mut guard = self.0.lock().await; match &mut*guard { ConnectionWriterState::Active(sender) => { // I have to clone it :( if let Err(_) = sender.send(msg.clone()).await { // Make the connection inactive let cell_arc = Arc::new(OnceCell::new()); *guard = ConnectionWriterState::Inactive(cell_arc.clone()); // Drop the reference and wait for it to become active drop(guard); cell_arc.wait().await; // Recursively try again self.send(msg); } }, ConnectionWriterState::Inactive(..) => { unreachable!() } } } async fn replace (&self, sender: SinkBox) { let mut guard = self.0.lock().await; let previous = std::mem::replace(&mut*guard, ConnectionWriterState::Active(sender)); // Signal the writer that the connection was replaced if let ConnectionWriterState::Inactive(cell) = previous { let _ = cell.set(()).await; // Signal the writer that the connection was replaced } } } impl Request<'_> { pub fn add_field(&mut self, key: &str, value: impl Into) { self.body.insert(key.to_owned(), value.into()); } /// Sends the request and returns its nonce for follow up async fn send_only(self) { let body = self.body; let json_payload: JsonValue = JsonValue::from(HashMap::from([ ("nonce".to_owned(), (self.nonce as f64).into()), ("action".to_owned(), JsonValue::from(self.action.to_owned())), ("body".to_owned(), JsonValue::from(body)), ])); let text_payload = json_payload.stringify().unwrap(); self.horde .controller .sender .send(Message::Text(text_payload)) .await .unwrap(); } pub async fn send_recv_value(self) -> JsonObject { let horde = self.horde; let (listener, recv) = RequestListener::value(); horde.listen(self.nonce, listener).await; self.send_only().await; recv.recv().await.unwrap().try_into().unwrap() } pub async fn send_recv_stream(self) -> impl smol::stream::Stream { let horde = self.horde; let (listener, recv) = RequestListener::stream(); horde.listen(self.nonce, listener).await; self.send_only().await; recv } pub async fn send_forget(self) { self.send_only().await; } } impl Horde { pub async fn connect(host: &str) -> Result { // Format hosts for each connection let ws_host = format!("{}/ws", host); let tcp_host = if host.starts_with("ws://") { host.split_at_checked(5).unwrap().1 } else if host.starts_with("wss://") { host.split_at_checked(6).unwrap().1 } else { &host }.to_owned(); let tcp_stream = TcpStream::connect(&tcp_host).await.unwrap(); let (ws_stream, _) = client_async_tls(&ws_host, tcp_stream).await.unwrap(); let (ws_sender, mut ws_receiver) = ws_stream.split(); let connection_out = Arc::new(ConnectionWriter(Mutex::new( ConnectionWriterState::Active(Box::new(ws_sender)) ))); let (sender, receiver) = channel::unbounded(); let controller = Arc::new(Controller { nonce: 0.into(), sender, nonce_data: Mutex::new(HashMap::new()), credential: RwLock::new(Credential::Empty), }); let horde = Horde { controller: controller.clone() }; // Forward received messages to the websocket connection let out_clone = connection_out.clone(); smol::spawn(async move { while let Ok(msg) = receiver.recv().await { out_clone.send(msg).await; } }) .detach(); let horde_clone = horde.clone(); smol::spawn(async move { loop { { // Cannot read from a stream unless it is pinned // Pin it only in the context where it's receiving smol::pin!(ws_receiver); while let Some(Ok(msg)) = ws_receiver.next().await { match msg { Message::Text(text) => { if let Some(incoming) = Horde::parse_incoming(&text) { let mut listeners = controller.nonce_data.lock().await; if let Some(nonce) = incoming.nonce { if let Some(info) = listeners.get_mut(&nonce) { if info.listener.is_some() { RequestListener::feed(&mut info.listener, incoming.value).await } else { info.queue.push(incoming.value); } } else { listeners.insert( nonce, NonceInfo { queue: vec![incoming.value], listener: None, }, ); } } else { // Message without nonce } } else { println!("Bad message: {}", text); } }, _ => { println!("Other Message: {:?}", msg); } } } } let tcp_stream = TcpStream::connect(&tcp_host).await.unwrap(); let (ws_stream, _) = client_async_tls(&ws_host, tcp_stream).await.unwrap(); let (ws_sender, mut new_receiver) = ws_stream.split(); ws_receiver = new_receiver; connection_out.replace(Box::new(ws_sender)).await; horde.auth(horde.controller.credential.read().await.clone()).await; } }) .detach(); Ok(horde_clone) } pub async fn with_token(host: &str, token: String) -> Result { let horde = Self::connect(host).await?; let credential = Credential::Token(token); horde.set_credential(credential.clone()).await; horde.auth(credential).await; Ok(horde) } pub async fn connect_user(host: &str, username: String, password: String) -> Result { let horde = Self::connect(host).await?; let credential = Credential::User{username, password}; horde.set_credential(credential.clone()).await; horde.auth(credential).await; Ok(horde) } async fn set_credential (&self, credential: Credential) { let mut writer = self.controller.credential.write().await; *writer = credential.clone(); } async fn auth (&self, credential: Credential) { match credential { Credential::Token(token) => { let req_obj: JsonValue = JsonObject::from([ ("token".to_owned(), token.into()), ]).into(); let text_payload = req_obj.stringify().unwrap(); self.controller .sender .send(Message::Text(text_payload)) .await .unwrap(); }, Credential::User{username, password} => { let req_obj: JsonValue = JsonObject::from([ ("username".to_owned(), username.into()), ("password".to_owned(), password.into()), ]).into(); let text_payload = req_obj.stringify().unwrap(); self.controller .sender .send(Message::Text(text_payload)) .await .unwrap(); }, Credential::Empty => { println!("Empty credentials"); }, } } pub fn request<'a>(&'a self, action: &'a str) -> Request<'a> { Request { nonce: self .controller .nonce .fetch_add(1, std::sync::atomic::Ordering::SeqCst), horde: &self, action, body: JsonObject::new(), } } fn parse_incoming(message: &str) -> Option { let json_parsed: JsonValue = message.parse().ok()?; let mut response = json_parsed.get::()?.clone(); let nonce = response.remove("nonce") .and_then(|jval| jval.get::().copied()) .map(|x| x as u32); Some(Incoming { nonce, value: response.into(), }) } async fn listen(&self, nonce: u32, listener: RequestListener) { let mut nonce_data = self.controller.nonce_data.lock().await; // Check if there are values in the queue for the specified nonce if let Some(info) = nonce_data.get_mut(&nonce) { let mut maybe_listener = Some(listener); if info.listener.is_some() { panic!("Multiple listeners for same nonce: {}", nonce); } while !info.queue.is_empty() && maybe_listener.is_some() { let value = info.queue.remove(0); RequestListener::feed(&mut maybe_listener, value).await; } info.listener = maybe_listener; } else { nonce_data.insert( nonce, NonceInfo { queue: vec![], listener: Some(listener), }, ); } } pub async fn handle(&self, action: &str, inputs: JsonObject) -> JsonObject { let mut req = self.request(action); req.body = inputs; let res = req.send_recv_value().await; //println!("handle response: {:?}", res); if let Some(message_value) = res.get("error") { panic!("Hanlde Error: {:?}", message_value); } return res; //res.get("output").unwrap().get::().unwrap().clone() } pub async fn read_stream(&self, stream_id: JsonValue) -> impl Stream { let mut request = self.request("read-stream"); request.add_field("streamId", stream_id); let receiver = request.send_recv_stream().await; async_stream::stream! { smol::pin!(receiver); while let Some(mut packet) = receiver.next().await { let packet_obj = packet.get_mut::().unwrap(); // Detect end of stream if let Some(value) = packet_obj.get("eof") { if *value.get().unwrap() { break; } } let data = packet_obj.remove("data").unwrap(); yield String::try_from(data).unwrap(); } } } }