From 883e491a7f452a8e394fe296110764b3927125cc Mon Sep 17 00:00:00 2001 From: Laurent Pelecq Date: Fri, 18 Mar 2022 15:39:01 +0100 Subject: [PATCH] Add all requests in AsyncClient. * Simplify fifo tests with tempfile. * Test getter in async fifo tests. --- Cargo.toml | 2 + src/async_mio.rs | 133 ++++++++++++++++++++++++++++++++++++++++++++--- src/client.rs | 6 +-- src/lib.rs | 1 - src/protocol.rs | 1 + src/types.rs | 20 +++---- 6 files changed, 142 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4b2fe5b..5b1d922 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,5 @@ async-mio = ["mio/net", "mio/os-poll"] [dev-dependencies] libc = "0" mio = { version = "0.8", features = ["os-poll", "os-ext"] } +tempfile = "3" + diff --git a/src/async_mio.rs b/src/async_mio.rs index 4a48045..cf6f2ab 100644 --- a/src/async_mio.rs +++ b/src/async_mio.rs @@ -14,18 +14,53 @@ use std::io::{self, Read, Write}; use crate::{ client::{Client, ClientError, ClientName, ClientResult}, constants::*, - types::{EventId, SynthesisVoice}, + types::*, }; -#[derive(Debug)] +#[derive(Debug, Clone)] /// Request for SSIP server. pub enum Request { SetName(ClientName), + // Speech related requests Speak, SendLine(String), SendLines(Vec), SendChar(char), + // Flow control + Stop(MessageScope), + Cancel(MessageScope), + Pause(MessageScope), + Resume(MessageScope), + // Setter and getter + SetPriority(Priority), + SetDebug(bool), + SetOutputModule(ClientScope, String), + GetOutputModule, + ListOutputModule, + SetLanguage(ClientScope, String), + GetLanguage, + SetSsmlMode(bool), + SetPunctuationMode(ClientScope, PunctuationMode), + SetSpelling(ClientScope, bool), + SetCapitalLettersRecognitionMode(ClientScope, CapitalLettersRecognitionMode), + SetVoiceType(ClientScope, String), + GetVoiceType, + ListVoiceTypes, + SetSynthesisVoice(ClientScope, String), + ListSynthesisVoices, + SetRate(ClientScope, i8), + GetRate, + SetPitch(ClientScope, i8), + GetPitch, + SetVolume(ClientScope, i8), + GetVolume, + SetPauseContext(ClientScope, u8), + SetHistory(ClientScope, bool), + Begin, + End, Quit, + EnableNotification(NotificationType), + DisableNotification(NotificationType), } #[derive(Debug)] @@ -73,7 +108,7 @@ pub enum Response { VoicesListSent(Vec), // 249 OutputModulesListSent(Vec), // 250 GetString(String), // 251 - GetInteger(u8), // 251 + GetInteger(i8), // 251 InsideBlock, // 260 OutsideBlock, // 261 NotImplemented, // 299 @@ -87,12 +122,18 @@ pub enum Response { const INITIAL_REQUEST_QUEUE_CAPACITY: usize = 4; +enum GetType { + StringType, + IntegerType, +} + /// Asynchronous client based on `mio`. /// /// pub struct AsyncClient { client: Client, requests: VecDeque, + get_types: VecDeque, } impl AsyncClient { @@ -101,6 +142,7 @@ impl AsyncClient { Self { client, requests: VecDeque::with_capacity(INITIAL_REQUEST_QUEUE_CAPACITY), + get_types: VecDeque::with_capacity(INITIAL_REQUEST_QUEUE_CAPACITY), } } @@ -133,6 +175,16 @@ impl AsyncClient { !self.requests.is_empty() } + /// Next get is a string. + fn push_get_string(&mut self) { + self.get_types.push_back(GetType::StringType); + } + + /// Next get is an integer. + fn push_get_int(&mut self) { + self.get_types.push_back(GetType::IntegerType); + } + /// Write one pending request if any. /// /// Instance of `mio::Poll` generates a writable event only once until the socket returns `WouldBlock`. @@ -151,7 +203,67 @@ impl AsyncClient { .as_slice(), ), Request::SendChar(ch) => self.client.send_char(ch), + Request::Stop(scope) => self.client.stop(scope), + Request::Cancel(scope) => self.client.cancel(scope), + Request::Pause(scope) => self.client.pause(scope), + Request::Resume(scope) => self.client.resume(scope), + Request::SetPriority(prio) => self.client.set_priority(prio), + Request::SetDebug(value) => self.client.set_debug(value), + Request::SetOutputModule(scope, value) => { + self.client.set_output_module(scope, &value) + } + Request::GetOutputModule => { + self.push_get_string(); + self.client.get_output_module() + } + Request::ListOutputModule => self.client.list_output_modules(), + Request::SetLanguage(scope, lang) => self.client.set_language(scope, &lang), + Request::GetLanguage => { + self.push_get_string(); + self.client.get_language() + } + Request::SetSsmlMode(value) => self.client.set_ssml_mode(value), + Request::SetPunctuationMode(scope, mode) => { + self.client.set_punctuation_mode(scope, mode) + } + Request::SetSpelling(scope, value) => self.client.set_spelling(scope, value), + Request::SetCapitalLettersRecognitionMode(scope, mode) => { + self.client.set_capital_letter_recogn(scope, mode) + } + Request::SetVoiceType(scope, value) => self.client.set_voice_type(scope, &value), + Request::GetVoiceType => { + self.push_get_string(); + self.client.get_voice_type() + } + Request::ListVoiceTypes => self.client.list_voice_types(), + Request::SetSynthesisVoice(scope, value) => { + self.client.set_synthesis_voice(scope, &value) + } + Request::ListSynthesisVoices => self.client.list_synthesis_voices(), + Request::SetRate(scope, value) => self.client.set_rate(scope, value), + Request::GetRate => { + self.push_get_int(); + self.client.get_rate() + } + Request::SetPitch(scope, value) => self.client.set_pitch(scope, value), + Request::GetPitch => { + self.push_get_int(); + self.client.get_pitch() + } + Request::SetVolume(scope, value) => self.client.set_volume(scope, value), + Request::GetVolume => { + self.push_get_int(); + self.client.get_volume() + } + Request::SetPauseContext(scope, value) => { + self.client.set_pause_context(scope, value) + } + Request::SetHistory(scope, value) => self.client.set_history(scope, value), Request::Quit => self.client.quit(), + Request::Begin => self.client.block_begin(), + Request::End => self.client.block_end(), + Request::EnableNotification(ntype) => self.client.enable_notification(ntype), + Request::DisableNotification(ntype) => self.client.disable_notification(ntype), } .map(|_| ()), None => Ok(()), @@ -225,10 +337,17 @@ impl AsyncClient { OK_OUTPUT_MODULES_LIST_SENT => Ok(Response::OutputModulesListSent(lines)), OK_GET => { let sval = Client::::parse_single_value(&lines)?; - Ok(match sval.parse::() { - Ok(uval) => Response::GetInteger(uval), - Err(_) => Response::GetString(sval), - }) + match self + .get_types + .pop_front() + .expect("internal error: get_types is empty") + { + GetType::StringType => Ok(Response::GetString(sval)), + GetType::IntegerType => sval + .parse::() + .map(|uval| Response::GetInteger(uval)) + .map_err(|_| ClientError::InvalidType), + } } OK_INSIDE_BLOCK => Ok(Response::InsideBlock), OK_OUTSIDE_BLOCK => Ok(Response::OutsideBlock), diff --git a/src/client.rs b/src/client.rs index fa63b2f..5a65e79 100644 --- a/src/client.rs +++ b/src/client.rs @@ -63,7 +63,7 @@ pub type ClientResult = Result; pub type ClientStatus = ClientResult; /// Client name -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ClientName { pub user: String, pub application: String, @@ -417,6 +417,8 @@ impl Client { "SET {} VOLUME {}" ); + client_send!(get_volume, "Get the current volume.", "GET VOLUME"); + client_send!( set_pause_context, "Set the number of (more or less) sentences that should be repeated after a previously paused text is resumed.", @@ -433,8 +435,6 @@ impl Client { "SET {} HISTORY {}" ); - client_send!(get_volume, "Get the current volume.", "GET VOLUME"); - client_send!(block_begin, "Open a block", "BLOCK BEGIN"); client_send!(block_end, "End a block", "BLOCK END"); diff --git a/src/lib.rs b/src/lib.rs index c48cc50..2fcd24f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,6 @@ pub use client::Client; pub use client::{ClientError, ClientName, ClientResult, ClientStatus}; pub use constants::*; -pub use types::StatusLine; pub use types::*; #[cfg(any(feature = "async-mio", doc))] diff --git a/src/protocol.rs b/src/protocol.rs index 8dbd5e5..5b7611a 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -80,6 +80,7 @@ pub(crate) fn receive_answer( return Err(invalid_input!("expecting space or dash, got {}.", ch)); } }, + None if line.is_empty() => return Err(invalid_input!("empty line")), None => return Err(invalid_input!("line too short: {}", line)), } } diff --git a/src/types.rs b/src/types.rs index 42b46b9..ebcddf2 100644 --- a/src/types.rs +++ b/src/types.rs @@ -22,7 +22,7 @@ pub type MessageId = String; pub type ClientId = String; /// Message identifiers -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum MessageScope { /// Last message from current client Last, @@ -33,7 +33,7 @@ pub enum MessageScope { } /// Client identifiers -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ClientScope { /// Current client Current, @@ -44,7 +44,7 @@ pub enum ClientScope { } /// Priority -#[derive(StrumDisplay, Debug)] +#[derive(StrumDisplay, Debug, Clone)] pub enum Priority { #[strum(serialize = "progress")] Progress, @@ -59,7 +59,7 @@ pub enum Priority { } /// Punctuation mode. -#[derive(StrumDisplay, Debug)] +#[derive(StrumDisplay, Debug, Clone)] pub enum PunctuationMode { #[strum(serialize = "none")] None, @@ -72,7 +72,7 @@ pub enum PunctuationMode { } /// Capital letters recognition mode. -#[derive(StrumDisplay, Debug)] +#[derive(StrumDisplay, Debug, Clone)] pub enum CapitalLettersRecognitionMode { #[strum(serialize = "none")] None, @@ -83,7 +83,7 @@ pub enum CapitalLettersRecognitionMode { } /// Symbolic key names -#[derive(StrumDisplay, Debug)] +#[derive(StrumDisplay, Debug, Clone)] pub enum KeyName { #[strum(serialize = "space")] Space, @@ -230,7 +230,7 @@ pub enum KeyName { } /// Notification type -#[derive(StrumDisplay, Debug)] +#[derive(StrumDisplay, Debug, Clone)] pub enum NotificationType { #[strum(serialize = "begin")] Begin, @@ -249,7 +249,7 @@ pub enum NotificationType { } /// Notification event type (returned by server) -#[derive(StrumDisplay, Debug)] +#[derive(StrumDisplay, Debug, Clone)] pub enum EventType { Begin, End, @@ -260,7 +260,7 @@ pub enum EventType { } /// Event identifier -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EventId { // Message id pub message: String, @@ -279,7 +279,7 @@ impl EventId { } /// Notification event -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Event { pub ntype: EventType, pub id: EventId,