diff --git a/src/rust/amadeus-rs/amadeus/src/utils/deamon.rs b/src/rust/amadeus-rs/amadeus/src/utils/deamon.rs index 252aaf3b227a6009792605a6b9ac1da40d229a58..a929b26ac7fddd95d69ec0e58fd8a9e1adf35d1b 100644 --- a/src/rust/amadeus-rs/amadeus/src/utils/deamon.rs +++ b/src/rust/amadeus-rs/amadeus/src/utils/deamon.rs @@ -161,11 +161,15 @@ impl Deamon for StatusDeamon { } }; match LektorPlaybackStatusResponse::from_formated(res) { + Ok(LektorResponse::PlaybackStatus(res)) => res, + Ok(_) => { + error!("got invalid response from lektor, not a status..."); + continue; + } Err(e) => { error!("failed to build response from formated response: {e}"); continue; } - Ok(res) => res, } }; @@ -178,7 +182,11 @@ impl Deamon for StatusDeamon { } }; match LektorCurrentKaraResponse::from_formated(res) { - Ok(res) => Some(res), + Ok(LektorResponse::CurrentKara(res)) => Some(res), + Ok(_) => { + error!("got invalid response from lektor, not a current kara..."); + None + } Err(err) => { error!("failed to build response from formated response: {err}"); None diff --git a/src/rust/amadeus-rs/lkt-lib/src/connexion.rs b/src/rust/amadeus-rs/lkt-lib/src/connexion.rs index 4a3edbc85c2fa40685e340a723bf12635efbc8f8..96d14c2b5ea5785ac418f8897588d1de903f1063 100644 --- a/src/rust/amadeus-rs/lkt-lib/src/connexion.rs +++ b/src/rust/amadeus-rs/lkt-lib/src/connexion.rs @@ -124,6 +124,7 @@ impl LektorConnexion { Ok(LektorQueryLineType::Ok) => return Ok((ret, None)), Ok(LektorQueryLineType::Ack) => return error_return_value, Ok(LektorQueryLineType::Data) => ret.push(msg.to_string()), + Ok(LektorQueryLineType::ListOk) => continue, Ok(LektorQueryLineType::Continuation(cont)) => { return Ok((ret, Some(cont))) } diff --git a/src/rust/amadeus-rs/lkt-lib/src/query.rs b/src/rust/amadeus-rs/lkt-lib/src/query.rs index f0fe4dac1f6932d814857f68c5d10bdf020b70d7..55eee15ffd0e3b2d72713e3f0fa3bf54273ea807 100644 --- a/src/rust/amadeus-rs/lkt-lib/src/query.rs +++ b/src/rust/amadeus-rs/lkt-lib/src/query.rs @@ -1,11 +1,12 @@ //! Contains files to create and build queries to send latter to lektord. -use crate::uri::LektorUri; -use amadeus_macro::lkt_command_from_str; -use std::{borrow::Borrow, string::ToString}; +use crate::*; +use amadeus_macro::*; +use std::string::ToString; pub(crate) enum LektorQueryLineType { Ok, + ListOk, Ack, Continuation(usize), Data, @@ -44,6 +45,8 @@ impl std::str::FromStr for LektorQueryLineType { Ok(Self::Ok) } else if Self::is_line_ack(line) { Ok(Self::Ack) + } else if Self::is_line_list_ok(line) { + Ok(Self::ListOk) } else if let Some(cont) = Self::is_line_continuation(line) { Ok(Self::Continuation(cont)) } else { @@ -54,14 +57,17 @@ impl std::str::FromStr for LektorQueryLineType { impl LektorQueryLineType { fn is_line_continuation(line: &str) -> Option<usize> { - if line.starts_with("continue:") { - match line.trim_start_matches("continue:").trim().parse::<usize>() { - Ok(cont) => Some(cont), - Err(_) => None, - } - } else { - None + if !line.starts_with("continue:") { + return None; } + match line.trim_start_matches("continue:").trim().parse::<usize>() { + Ok(cont) => Some(cont), + Err(_) => None, + } + } + + fn is_line_list_ok(line: &str) -> bool { + (line == "list_OK\n") || (line == "list_OK") } fn is_line_ok(line: &str) -> bool { @@ -73,11 +79,41 @@ impl LektorQueryLineType { } } +/// The type of the function to use to produce a typed response from the +/// formated response. +type QueryToTypeResponseBuilder = fn(LektorFormatedResponse) -> Result<LektorResponse, String>; + impl LektorQuery { + /// Get the function to use to produce the typed response from the formated + /// response, to automatically create the correct thing. + pub fn get_response_type(&self) -> QueryToTypeResponseBuilder { + use LektorQuery::*; + match self { + Ping | Close | KillServer | PlayNext | PlayPrevious | ShuffleQueue | InsertKara(_) + | AddKara(_) => LektorEmptyResponse::from_formated, + + ListAllPlaylists => LektorPlaylistListResponse::from_formated, + PlaybackStatus => LektorPlaybackStatusResponse::from_formated, + CurrentKara => LektorCurrentKaraResponse::from_formated, + + ConnectAsUser(_, cmd) | Continuation(_, cmd) => cmd.get_response_type(), + + ListPlaylist(_) | SearchKara(_) | FindAddKara(_) => { + LektorKaraSetResponse::from_formated + } + } + } + + /// Create a continued query out of another one. If the query is already a + /// continuation query then the underlying query is reused. pub fn create_continuation(query: Self, cont: usize) -> Self { - Self::Continuation(cont, Box::new(query)) + match query { + Self::Continuation(_, query) => Self::Continuation(cont, query), + _ => Self::Continuation(cont, Box::new(query)), + } } + /// Verify that a query is Ok. pub fn verify(&self) -> Result<(), String> { use LektorQuery::*; match self { @@ -91,13 +127,13 @@ impl LektorQuery { KillServer => Err("kill server is an admin command".to_string()), // Admin commands - ConnectAsUser(_, cmd) => match cmd.borrow() { + ConnectAsUser(_, cmd) => match cmd.as_ref() { Close | KillServer => Ok(()), _ => Err(format!("not an admin command: {cmd:?}")), }, // Continuation commands - Continuation(_, cmd) => match cmd.borrow() { + Continuation(_, cmd) => match cmd.as_ref() { ListAllPlaylists | FindAddKara(_) | SearchKara(_) | ListPlaylist(_) => Ok(()), _ => Err(format!("not a continuable command: {cmd:?}")), }, @@ -114,7 +150,7 @@ impl ToString for LektorQuery { KillServer => lkt_command_from_str!("kill"), ConnectAsUser(password, cmd) => format!( concat!( - "command_list_begin\n", + "command_list_ok_begin\n", "password {}\n", "{}\n", "command_list_end\n", diff --git a/src/rust/amadeus-rs/lkt-lib/src/response.rs b/src/rust/amadeus-rs/lkt-lib/src/response.rs index 1765bc64a4604eca29f5f809753069edc42c28f0..0bcffefa4644635dd0a111cb684ef638e13dc09e 100644 --- a/src/rust/amadeus-rs/lkt-lib/src/response.rs +++ b/src/rust/amadeus-rs/lkt-lib/src/response.rs @@ -9,6 +9,7 @@ use amadeus_macro::*; #[derive(Debug)] pub struct LektorFormatedResponse { content: Vec<(String, String)>, + raw_content: Vec<String>, } impl LektorFormatedResponse { @@ -42,6 +43,11 @@ impl LektorFormatedResponse { }); ret } + + /// Get the raw content of the response. + pub fn pop_raw(self) -> Vec<String> { + self.raw_content + } } impl IntoIterator for LektorFormatedResponse { @@ -58,34 +64,39 @@ impl TryFrom<Vec<String>> for LektorFormatedResponse { type Error = String; fn try_from(vec: Vec<String>) -> Result<Self, Self::Error> { - let mut content = Vec::with_capacity(vec.len()); + let (mut content, mut raw_content) = (Vec::new(), Vec::new()); for line in vec { let key_pair: Vec<&str> = line.splitn(2, ':').collect(); match key_pair[..] { - [key, value] => { - content.push((key.trim().to_lowercase(), value.trim().to_string())); - } - _ => return Err(format!("Invalid line for formated response: {}", line)), + [key, value] => content.push((key.trim().to_lowercase(), value.trim().to_string())), + _ => raw_content.push(line), } } - Ok(Self { content }) + Ok(Self { + content, + raw_content, + }) } } #[derive(Debug)] -pub enum LektordResponse { +pub enum LektorResponse { PlaybackStatus(LektorPlaybackStatusResponse), CurrentKara(LektorCurrentKaraResponse), PlaylistList(LektorPlaylistListResponse), + EmptyResponse(LektorEmptyResponse), + KaraSet(LektorKaraSetResponse), } /// A trait for typed lektor responses. Such responses must be built by /// consuming a formated response. We also protect from implemeting this trait /// outside of this crate. -pub trait FromLektorResponse: Sized + std::fmt::Debug + private::Sealed { +pub trait FromLektorResponse: std::fmt::Debug + private::Sealed { /// Consume a formated response to produce the correctly typed response. May /// got an error as a string that describes the problem. - fn from_formated(response: LektorFormatedResponse) -> Result<Self, String>; + fn from_formated(response: LektorFormatedResponse) -> Result<LektorResponse, String> + where + Self: Sized; } mod private { @@ -94,6 +105,7 @@ mod private { impl Sealed for LektorPlaybackStatusResponse {} impl Sealed for LektorCurrentKaraResponse {} impl Sealed for LektorPlaylistListResponse {} + impl Sealed for LektorKaraSetResponse {} impl Sealed for LektorEmptyResponse {} } @@ -143,6 +155,11 @@ pub struct LektorCurrentKaraInnerResponse { language: String, } +#[derive(Debug)] +pub struct LektorKaraSetResponse { + karas: Vec<String>, +} + #[derive(Debug)] pub struct LektorCurrentKaraResponse { content: Option<LektorCurrentKaraInnerResponse>, @@ -180,16 +197,29 @@ impl LektorPlaybackStatusResponse { getter!(repeat: bool); } +impl LektorKaraSetResponse { + pub fn iter(&self) -> &[String] { + &self.karas[..] + } +} + impl LektorPlaylistListResponse { pub fn iter(&self) -> &[String] { &self.playlists[..] } } -impl IntoIterator for LektorPlaylistListResponse { +impl IntoIterator for LektorKaraSetResponse { type Item = String; type IntoIter = <std::vec::Vec<std::string::String> as IntoIterator>::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.karas.into_iter() + } +} +impl IntoIterator for LektorPlaylistListResponse { + type Item = String; + type IntoIter = <std::vec::Vec<std::string::String> as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.playlists.into_iter() } @@ -209,21 +239,32 @@ impl LektorCurrentKaraInnerResponse { } impl FromLektorResponse for LektorEmptyResponse { - fn from_formated(_: LektorFormatedResponse) -> Result<Self, String> { - Ok(Self {}) + fn from_formated(_: LektorFormatedResponse) -> Result<LektorResponse, String> { + Ok(LektorResponse::EmptyResponse(Self {})) } } impl FromLektorResponse for LektorPlaylistListResponse { - fn from_formated(mut response: LektorFormatedResponse) -> Result<Self, String> { - Ok(Self { + fn from_formated(mut response: LektorFormatedResponse) -> Result<LektorResponse, String> { + Ok(LektorResponse::PlaylistList(Self { playlists: response.pop_all("name"), - }) + })) + } +} + +impl FromLektorResponse for LektorKaraSetResponse { + fn from_formated(response: LektorFormatedResponse) -> Result<LektorResponse, String> + where + Self: Sized, + { + Ok(LektorResponse::KaraSet(Self { + karas: response.pop_raw(), + })) } } impl FromLektorResponse for LektorPlaybackStatusResponse { - fn from_formated(mut response: LektorFormatedResponse) -> Result<Self, String> { + fn from_formated(mut response: LektorFormatedResponse) -> Result<LektorResponse, String> { let mut ret = Self { elapsed: response.pop("elapsed")?.parse::<usize>().unwrap_or(0), songid: match response.pop("songid")?.parse::<isize>() { @@ -254,12 +295,12 @@ impl FromLektorResponse for LektorPlaybackStatusResponse { "pause" => LektorState::Pause(ret.songid.unwrap()), _ => LektorState::Stopped, }; - Ok(ret) + Ok(LektorResponse::PlaybackStatus(ret)) } } impl FromLektorResponse for LektorCurrentKaraResponse { - fn from_formated(mut response: LektorFormatedResponse) -> Result<Self, String> { + fn from_formated(mut response: LektorFormatedResponse) -> Result<LektorResponse, String> { let song_type_number = response.pop("type")?; let (song_type, song_number) = match song_type_number.find(char::is_numeric) { Some(index) => ( @@ -282,8 +323,8 @@ impl FromLektorResponse for LektorCurrentKaraResponse { song_number, }; - Ok(Self { + Ok(LektorResponse::CurrentKara(Self { content: then_some!(!inner.is_partial() => inner), - }) + })) } }