From ec1b1aca51bd81f939891a822054209929e39b15 Mon Sep 17 00:00:00 2001 From: Laurent Pelecq Date: Thu, 17 Mar 2022 23:31:40 +0100 Subject: [PATCH] New AsyncClient class. * Only few commands are implemented. * Example async_mio_loop added. --- Cargo.toml | 9 +- examples/async_mio_loop.rs | 103 +++++++++++++++ examples/hello.rs | 163 +----------------------- examples/notifications.rs | 2 +- src/async_mio.rs | 253 +++++++++++++++++++++++++++++++++++++ src/client.rs | 57 +++++---- src/lib.rs | 11 +- src/types.rs | 39 ++++-- tests/fifo_sync_tests.rs | 3 +- 9 files changed, 434 insertions(+), 206 deletions(-) create mode 100644 examples/async_mio_loop.rs create mode 100644 src/async_mio.rs diff --git a/Cargo.toml b/Cargo.toml index ea65bd0..4b2fe5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ssip-client" -version = "0.3.0" +version = "0.4.0" authors = ["Laurent Pelecq "] edition = "2018" description = "Client API for Speech Dispatcher" @@ -17,8 +17,9 @@ thiserror = "1" strum = "0.24" strum_macros = "0.24" -[dev-dependencies] -libc = "0" - [features] async-mio = ["mio/net", "mio/os-poll"] + +[dev-dependencies] +libc = "0" +mio = { version = "0.8", features = ["os-poll", "os-ext"] } diff --git a/examples/async_mio_loop.rs b/examples/async_mio_loop.rs new file mode 100644 index 0000000..4bab6bf --- /dev/null +++ b/examples/async_mio_loop.rs @@ -0,0 +1,103 @@ +#[cfg(feature = "async-mio")] +use mio::{unix::SourceFd, Events, Interest, Poll, Token}; +#[cfg(feature = "async-mio")] +use std::{ + collections::VecDeque, + io::{self, Write}, + os::unix::io::AsRawFd, +}; + +#[cfg(feature = "async-mio")] +use ssip_client::{fifo, AsyncClient, ClientError, ClientName, ClientResult, Request, Response}; + +#[cfg(feature = "async-mio")] +fn main() -> ClientResult<()> { + let stdin = io::stdin(); + + // Poll instance + let mut poll = Poll::new()?; + + // Register stdin + let stdin_fd = stdin.as_raw_fd(); + let mut source_fd = SourceFd(&stdin_fd); + let stdin_token = Token(0); + poll.registry() + .register(&mut source_fd, stdin_token, Interest::READABLE)?; + + // Register the SSIP client + let mut ssip_client = AsyncClient::new(fifo::Builder::new().build()?); + let speech_input_token = Token(1); + let speech_output_token = Token(2); + ssip_client.register(&poll, speech_input_token, speech_output_token)?; + + // Loop for events + let mut events = Events::with_capacity(16); + let mut speech_writable = false; + let mut send_requests = VecDeque::with_capacity(4); + ssip_client.push(Request::SetName(ClientName::new("joe", "async"))); + + fn prompt() -> io::Result<()> { + let mut stdout = io::stdout(); + write!(stdout, "> ")?; + stdout.flush() + } + + println!("Enter an empty line to quit."); + prompt()?; + loop { + if !speech_writable || !ssip_client.has_next() { + poll.poll(&mut events, None)?; + } + for event in &events { + let token = event.token(); + if token == stdin_token { + let mut text = String::new(); + stdin.read_line(&mut text)?; + text = text.trim_end().to_string(); + match text.len() { + 0 => return Ok(()), + 1 => { + if let Some(ch) = text.chars().next() { + println!("sending char: {}", ch); + ssip_client.push(Request::SendChar(ch)) + } + } + _ => { + println!("sending line: {}", text); + send_requests.push_back(Request::SendLine(text.to_owned())); + ssip_client.push(Request::Speak); + } + } + prompt()?; + } else if token == speech_input_token { + match ssip_client.receive_next() { + Err(ClientError::Io(err)) => return Err(ClientError::from(err)), + Err(ClientError::Ssip(err)) => eprintln!("SSIP error: {:?}", err), + Err(_) => panic!("internal error"), + Ok(result) => match result { + Response::MessageQueued | Response::ClientNameSet => (), + Response::ReceivingData => { + ssip_client.push(send_requests.pop_front().unwrap()) + } + _ => panic!("Unexpected response: {:?}", result), + }, + } + } else if token == speech_output_token { + speech_writable = true; + } + } + if speech_writable { + match ssip_client.send_next() { + Err(ClientError::NotReady) => speech_writable = false, + Err(ClientError::Io(err)) => return Err(ClientError::from(err)), + Err(_) => panic!("internal error"), + Ok(()) => (), + } + } + } +} + +#[cfg(not(feature = "async-mio"))] +fn main() { + println!("see hello for an example of synchronous client."); +} diff --git a/examples/hello.rs b/examples/hello.rs index ec03016..2af6d10 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,9 +1,6 @@ +#[cfg(not(feature = "async-mio"))] use ssip_client::{fifo, ClientName, ClientResult}; -// ============================== -// Synchronous implementation -// ============================== - #[cfg(not(feature = "async-mio"))] fn main() -> ClientResult<()> { let mut client = fifo::Builder::new().build()?; @@ -22,161 +19,7 @@ fn main() -> ClientResult<()> { Ok(()) } -// ============================== -// Asynchronous implementation -// ============================== - #[cfg(feature = "async-mio")] -use mio::{Events, Poll, Token}; - -#[cfg(feature = "async-mio")] -mod control { - - use ssip_client::{ClientError, ClientResult}; - - /// Controller to follow the sequence of actions and keep the socket state. - pub struct Controller { - step: u16, - done: bool, - writable: bool, - next_is_read: bool, - } - - impl Controller { - pub fn new() -> Controller { - Controller { - step: 0, - done: false, - writable: false, - next_is_read: false, - } - } - - /// Current step to execute. - pub fn step(&self) -> u16 { - self.step - } - - /// Return true when done. - pub fn done(&self) -> bool { - self.done - } - - /// If the next action is a read or the socket is not writable. - pub fn must_poll(&self) -> bool { - self.next_is_read || !self.writable - } - - /// Record that the socket is writable. - pub fn set_writable(&mut self) { - self.writable = true; - } - - /// Stop. - pub fn stop(&mut self) { - self.done = true; - } - - /// Interpret the result of the action and move to the next step if necessary. - /// - /// When the socket is set to writable, no other writable event will be generated until - /// the I/O returns error WouldBlock which is mapped to client error NotReady. - pub fn next(&mut self, next_is_read: bool, result: ClientResult) -> ClientResult<()> { - match result { - Ok(_) => { - self.step += 1; - self.next_is_read = next_is_read; - Ok(()) - } - Err(ClientError::NotReady) => { - if !self.next_is_read { - // let's wait for the socket to become writable - self.writable = false; - } - Ok(()) - } - Err(err) => Err(err), - } - } - - /// Return the value returned by last read and move to next step. - pub fn get_value(&mut self, result: ClientResult) -> ClientResult> { - match result { - Ok(value) => { - self.step += 1; - self.next_is_read = false; - Ok(Some(value)) - } - Err(ClientError::NotReady) => Ok(None), - Err(err) => Err(err), - } - } - } -} - -#[cfg(feature = "async-mio")] -fn main() -> ClientResult<()> { - enum Action { - None, - Read, - Write, - } - - // Create the poll object, the client and register the socket. - let mut poll = Poll::new()?; - let mut events = Events::with_capacity(16); - let mut client = fifo::Builder::new().build()?; - let input_token = Token(0); - let output_token = Token(1); - client.register(&poll, input_token, output_token)?; - - let mut ctl = control::Controller::new(); - while !ctl.done() { - if ctl.must_poll() { - // Poll is only necessary to read or if the last write failed. - poll.poll(&mut events, None)?; - } - let mut event_iter = events.iter(); - loop { - let event = event_iter.next(); - let action = match event { - Some(event) if event.token() == output_token && event.is_writable() => { - ctl.set_writable(); - Action::Write - } - Some(event) if event.token() == input_token && event.is_readable() => Action::Read, - Some(_) => panic!("unexpected event"), - None if ctl.must_poll() => Action::None, - None => Action::Write, // Next action is write and socket is writable - }; - match action { - Action::Write => match ctl.step() { - 0 => ctl.next( - true, - client.set_client_name(ClientName::new("test", "test")), - )?, - 2 => ctl.next(true, client.speak())?, - 4 => ctl.next(true, client.send_line("hello"))?, - 6 => { - ctl.next(true, client.quit())?; - ctl.stop(); - break; - } - _ => (), - }, - Action::Read => match ctl.step() { - 1 => ctl.next(false, client.check_client_name_set())?, - 3 => ctl.next(false, client.check_receiving_data())?, - 5 => { - if let Some(msgid) = ctl.get_value(client.receive_message_id())? { - println!("Message identifier: {}", msgid); - } - } - _ => (), - }, - Action::None => break, - } - } - } - Ok(()) +fn main() { + println!("see async_mio_loop for an example of asynchronous client."); } diff --git a/examples/notifications.rs b/examples/notifications.rs index e87b04a..2f5c8c5 100644 --- a/examples/notifications.rs +++ b/examples/notifications.rs @@ -26,7 +26,7 @@ fn main() -> ClientResult<()> { Ok(event) => { println!( "event {}: message {} client {}", - event.ntype, event.message, event.client + event.ntype, event.id.message, event.id.client ); if matches!(event.ntype, EventType::End) { break; diff --git a/src/async_mio.rs b/src/async_mio.rs new file mode 100644 index 0000000..e44bdbc --- /dev/null +++ b/src/async_mio.rs @@ -0,0 +1,253 @@ +// ssip-client -- Speech Dispatcher client in Rust +// Copyright (c) 2022 Laurent Pelecq +// +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , at your +// option. All files in the project carrying such notice may not be copied, +// modified, or distributed except according to those terms. + +use std::collections::VecDeque; +use std::io::{self, Read, Write}; + +use crate::{ + client::{Client, ClientError, ClientName, ClientResult}, + constants::*, + types::{EventId, Source, SynthesisVoice}, +}; + +#[derive(Debug)] +/// Request for SSIP server. +pub enum Request { + SetName(ClientName), + Speak, + SendLine(String), + SendLines(Vec), + SendChar(char), + Quit, +} + +#[derive(Debug)] +/// Response from SSIP server. +pub enum Response { + LanguageSet, // 201 + PrioritySet, // 202 + RateSet, // 203 + PitchSet, // 204 + PunctuationSet, // 205 + CapLetRecognSet, // 206 + SpellingSet, // 207 + ClientNameSet, // 208 + VoiceSet, // 209 + Stopped, // 210 + Paused, // 211 + Resumed, // 212 + Canceled, // 213 + TableSet, // 215 + OutputModuleSet, // 216 + PauseContextSet, // 217 + VolumeSet, // 218 + SsmlModeSet, // 219 + NotificationSet, // 220 + PitchRangeSet, // 263 + DebugSet, // 262 + HistoryCurSetFirst, // 220 + HistoryCurSetLast, // 221 + HistoryCurSetPos, // 222 + HistoryCurMoveFor, // 223 + HistoryCurMoveBack, // 224 + MessageQueued, // 225, + SoundIconQueued, // 226 + MessageCanceled, // 227 + ReceivingData, // 230 + Bye, // 231 + HistoryClientListSent(Vec), // 240 + HistoryMsgsListSent(Vec), // 241 + HistoryLastMsg(String), // 242 + HistoryCurPosRet(String), // 243 + TableListSent(Vec), // 244 + HistoryClientIdSent(String), // 245 + MessageTextSent, // 246 + HelpSent(Vec), // 248 + VoicesListSent(Vec), // 249 + OutputModulesListSent(Vec), // 250 + GetString(String), // 251 + GetInteger(u8), // 251 + InsideBlock, // 260 + OutsideBlock, // 261 + NotImplemented, // 299 + EventIndexMark(EventId, String), // 700 + EventBegin(EventId), // 701 + EventEnd(EventId), // 702 + EventCanceled(EventId), // 703 + EventPaused(EventId), // 704 + EventResumed(EventId), // 705 +} + +const INITIAL_REQUEST_QUEUE_CAPACITY: usize = 4; + +/// Asynchronous client based on `mio`. +/// +/// +pub struct AsyncClient { + client: Client, + requests: VecDeque, +} + +impl AsyncClient { + /// New asynchronous client build on top of a synchronous client. + pub fn new(client: Client) -> Self { + Self { + client, + requests: VecDeque::with_capacity(INITIAL_REQUEST_QUEUE_CAPACITY), + } + } + + /// Convert two lines of the response in an event id + fn parse_event_id(lines: &[String]) -> ClientResult { + match lines.len() { + 0 | 1 => Err(ClientError::TooFewLines), + 2 => Ok(EventId::new(&lines[0], &lines[1])), + _ => Err(ClientError::TooManyLines), + } + } + + /// Register client + pub fn register( + &mut self, + poll: &mio::Poll, + input_token: mio::Token, + output_token: mio::Token, + ) -> io::Result<()> { + self.client.register(poll, input_token, output_token) + } + + /// Push a new request in the queue. + pub fn push(&mut self, request: Request) { + self.requests.push_back(request); + } + + /// Return true if there is a pending request. + pub fn has_next(&self) -> bool { + !self.requests.is_empty() + } + + /// Write one pending request if any. + /// + /// Instance of `mio::Poll` generates a writable event only once until the socket returns `WouldBlock`. + /// This error is mapped to `ClientError::NotReady`. + pub fn send_next(&mut self) -> ClientResult<()> { + match self.requests.pop_front() { + Some(request) => match request { + Request::SetName(client_name) => self.client.set_client_name(client_name), + Request::Speak => self.client.speak(), + Request::SendLine(line) => self.client.send_line(&line), + Request::SendLines(lines) => self.client.send_lines( + lines + .iter() + .map(|s| s.as_str()) + .collect::>() + .as_slice(), + ), + Request::SendChar(ch) => self.client.send_char(ch), + Request::Quit => self.client.quit(), + } + .map(|_| ()), + None => Ok(()), + } + } + + /// Receive one response. + /// + /// Must be called each time a readable event is returned by `mio::Poll`. + pub fn receive_next(&mut self) -> ClientResult { + const MSG_CURSOR_SET_FIRST: &str = "OK CURSOR SET FIRST"; + let mut lines = Vec::new(); + let status = self.client.receive(&mut lines)?; + match status.code { + OK_LANGUAGE_SET => Ok(Response::LanguageSet), + OK_PRIORITY_SET => Ok(Response::PrioritySet), + OK_RATE_SET => Ok(Response::RateSet), + OK_PITCH_SET => Ok(Response::PitchSet), + OK_PUNCTUATION_SET => Ok(Response::PunctuationSet), + OK_CAP_LET_RECOGN_SET => Ok(Response::CapLetRecognSet), + OK_SPELLING_SET => Ok(Response::SpellingSet), + OK_CLIENT_NAME_SET => Ok(Response::ClientNameSet), + OK_VOICE_SET => Ok(Response::VoiceSet), + OK_STOPPED => Ok(Response::Stopped), + OK_PAUSED => Ok(Response::Paused), + OK_RESUMED => Ok(Response::Resumed), + OK_CANCELED => Ok(Response::Canceled), + OK_TABLE_SET => Ok(Response::TableSet), + OK_OUTPUT_MODULE_SET => Ok(Response::OutputModuleSet), + OK_PAUSE_CONTEXT_SET => Ok(Response::PauseContextSet), + OK_VOLUME_SET => Ok(Response::VolumeSet), + OK_SSML_MODE_SET => Ok(Response::SsmlModeSet), + // Warning OK_CUR_SET_FIRST == OK_NOTIFICATION_SET == 220. Matching message to make the difference + OK_NOTIFICATION_SET => { + if status.message == MSG_CURSOR_SET_FIRST { + //OK_CUR_SET_FIRST => Ok(Response::HistoryCurSetFirst) + Ok(Response::HistoryCurSetFirst) + } else { + Ok(Response::NotificationSet) + } + } + + OK_CUR_SET_LAST => Ok(Response::HistoryCurSetLast), + OK_CUR_SET_POS => Ok(Response::HistoryCurSetPos), + OK_PITCH_RANGE_SET => Ok(Response::PitchRangeSet), + OK_DEBUG_SET => Ok(Response::DebugSet), + OK_CUR_MOV_FOR => Ok(Response::HistoryCurMoveFor), + OK_CUR_MOV_BACK => Ok(Response::HistoryCurMoveBack), + OK_MESSAGE_QUEUED => Ok(Response::MessageQueued), + OK_SND_ICON_QUEUED => Ok(Response::SoundIconQueued), + OK_MSG_CANCELED => Ok(Response::MessageCanceled), + OK_RECEIVING_DATA => Ok(Response::ReceivingData), + OK_BYE => Ok(Response::Bye), + OK_CLIENT_LIST_SENT => Ok(Response::HistoryClientListSent(lines)), + OK_MSGS_LIST_SENT => Ok(Response::HistoryMsgsListSent(lines)), + OK_LAST_MSG => Ok(Response::HistoryLastMsg(Client::::parse_single_value( + &lines, + )?)), + OK_CUR_POS_RET => Ok(Response::HistoryCurPosRet(Client::::parse_single_value( + &lines, + )?)), + OK_TABLE_LIST_SENT => Ok(Response::TableListSent(lines)), + OK_CLIENT_ID_SENT => Ok(Response::HistoryClientIdSent( + Client::::parse_single_value(&lines)?, + )), + OK_MSG_TEXT_SENT => Ok(Response::MessageTextSent), + OK_HELP_SENT => Ok(Response::HelpSent(lines)), + OK_VOICES_LIST_SENT => Ok(Response::VoicesListSent( + Client::::parse_synthesis_voices(&lines)?, + )), + 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), + }) + } + OK_INSIDE_BLOCK => Ok(Response::InsideBlock), + OK_OUTSIDE_BLOCK => Ok(Response::OutsideBlock), + OK_NOT_IMPLEMENTED => Ok(Response::NotImplemented), + EVENT_INDEX_MARK => { + if lines.len() == 3 { + Ok(Response::EventIndexMark( + Self::parse_event_id(&lines)?, + lines[2].to_owned(), + )) + } else { + Err(ClientError::TooFewLines) + } + } + EVENT_BEGIN => Ok(Response::EventBegin(Self::parse_event_id(&lines)?)), + EVENT_END => Ok(Response::EventEnd(Self::parse_event_id(&lines)?)), + EVENT_CANCELED => Ok(Response::EventCanceled(Self::parse_event_id(&lines)?)), + EVENT_PAUSED => Ok(Response::EventPaused(Self::parse_event_id(&lines)?)), + EVENT_RESUMED => Ok(Response::EventResumed(Self::parse_event_id(&lines)?)), + _ => panic!("error should have been caught earlier"), + } + } +} diff --git a/src/client.rs b/src/client.rs index 8549305..94e1daf 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,5 +1,5 @@ // ssip-client -- Speech Dispatcher client in Rust -// Copyright (c) 2021 Laurent Pelecq +// Copyright (c) 2021-2022 Laurent Pelecq // // Licensed under the Apache License, Version 2.0 // or the MIT @@ -176,7 +176,7 @@ impl Client { } /// Return the only string in the list or an error if there is no line or too many. - fn parse_single_value(lines: &[String]) -> ClientResult { + pub(crate) fn parse_single_value(lines: &[String]) -> ClientResult { match lines.len() { 0 => Err(ClientError::TooFewLines), 1 => Ok(lines[0].to_string()), @@ -184,8 +184,17 @@ impl Client { } } + pub(crate) fn parse_synthesis_voices(lines: &[String]) -> ClientResult> { + let mut voices = Vec::new(); + for name in lines.iter() { + let voice = SynthesisVoice::from_str(name.as_str())?; + voices.push(voice); + } + Ok(voices) + } + /// Set the client name. It must be the first call on startup. - pub fn set_client_name(&mut self, client_name: ClientName) -> ClientResult<&mut Client> { + pub fn set_client_name(&mut self, client_name: ClientName) -> ClientResult<&mut Self> { send_lines( &mut self.output, &[format!( @@ -198,13 +207,13 @@ impl Client { } /// Initiate communitation to send text to speak - pub fn speak(&mut self) -> ClientResult<&mut Client> { + pub fn speak(&mut self) -> ClientResult<&mut Self> { send_lines(&mut self.output, &["SPEAK"])?; Ok(self) } /// Send lines - pub fn send_lines(&mut self, lines: &[&str]) -> ClientResult<&mut Client> { + pub fn send_lines(&mut self, lines: &[&str]) -> ClientResult<&mut Self> { const END_OF_DATA: [&str; 1] = ["."]; write_lines(&mut self.output, lines)?; send_lines(&mut self.output, &END_OF_DATA)?; @@ -212,20 +221,20 @@ impl Client { } /// Send a line - pub fn send_line(&mut self, line: &str) -> ClientResult<&mut Client> { + pub fn send_line(&mut self, line: &str) -> ClientResult<&mut Self> { const END_OF_DATA: &str = "."; send_lines(&mut self.output, &[line, END_OF_DATA])?; Ok(self) } /// Send a char - pub fn send_char(&mut self, ch: char) -> ClientResult<&mut Client> { + pub fn send_char(&mut self, ch: char) -> ClientResult<&mut Self> { send_lines(&mut self.output, &[format!("CHAR {}", ch).as_str()])?; Ok(self) } /// Send a symbolic key name - pub fn say_key_name(&mut self, keyname: KeyName) -> ClientResult<&mut Client> { + pub fn say_key_name(&mut self, keyname: KeyName) -> ClientResult<&mut Self> { self.send_lines(&[format!("KEY {}", keyname).as_str()]) } @@ -234,7 +243,7 @@ impl Client { &mut self, command: &str, scope: MessageScope, - ) -> ClientResult<&mut Client> { + ) -> ClientResult<&mut Self> { let line = match scope { MessageScope::Last => format!("{} self", command), MessageScope::All => format!("{} all", command), @@ -245,22 +254,22 @@ impl Client { } /// Stop current message - pub fn stop(&mut self, scope: MessageScope) -> ClientResult<&mut Client> { + pub fn stop(&mut self, scope: MessageScope) -> ClientResult<&mut Self> { self.send_message_command("STOP", scope) } /// Cancel current message - pub fn cancel(&mut self, scope: MessageScope) -> ClientResult<&mut Client> { + pub fn cancel(&mut self, scope: MessageScope) -> ClientResult<&mut Self> { self.send_message_command("CANCEL", scope) } /// Pause current message - pub fn pause(&mut self, scope: MessageScope) -> ClientResult<&mut Client> { + pub fn pause(&mut self, scope: MessageScope) -> ClientResult<&mut Self> { self.send_message_command("PAUSE", scope) } /// Resume current message - pub fn resume(&mut self, scope: MessageScope) -> ClientResult<&mut Client> { + pub fn resume(&mut self, scope: MessageScope) -> ClientResult<&mut Self> { self.send_message_command("RESUME", scope) } @@ -445,7 +454,7 @@ impl Client { } /// Check status of answer, discard lines. - pub fn check_status(&mut self, expected_code: ReturnCode) -> ClientResult<&mut Client> { + pub fn check_status(&mut self, expected_code: ReturnCode) -> ClientResult<&mut Self> { crate::protocol::receive_answer(&mut self.input, None).and_then(|status| { if status.code == expected_code { Ok(self) @@ -469,7 +478,7 @@ impl Client { /// Receive a single string pub fn receive_string(&mut self, expected_code: ReturnCode) -> ClientResult { self.receive_lines(expected_code) - .and_then(|lines| Client::::parse_single_value(&lines)) + .and_then(|lines| Self::parse_single_value(&lines)) } /// Receive integer @@ -486,14 +495,8 @@ impl Client { /// Receive a list of synthesis voices pub fn receive_synthesis_voices(&mut self) -> ClientResult> { - self.receive_lines(OK_VOICES_LIST_SENT).and_then(|lines| { - let mut voices = Vec::new(); - for name in lines.iter() { - let voice = SynthesisVoice::from_str(name.as_str())?; - voices.push(voice); - } - Ok(voices) - }) + self.receive_lines(OK_VOICES_LIST_SENT) + .and_then(|lines| Self::parse_synthesis_voices(&lines)) } /// Receive a notification @@ -503,8 +506,8 @@ impl Client { if lines.len() < 2 { Err(ClientError::TruncatedMessage) } else { - let message = lines[0].to_owned(); - let client = lines[1].to_owned(); + let message = &lines[0]; + let client = &lines[1]; match status.code { 700 => { if lines.len() != 3 { @@ -526,12 +529,12 @@ impl Client { } /// Check the result of `set_client_name`. - pub fn check_client_name_set(&mut self) -> ClientResult<&mut Client> { + pub fn check_client_name_set(&mut self) -> ClientResult<&mut Self> { self.check_status(OK_CLIENT_NAME_SET) } /// Check if server accept data. - pub fn check_receiving_data(&mut self) -> ClientResult<&mut Client> { + pub fn check_receiving_data(&mut self) -> ClientResult<&mut Self> { self.check_status(OK_RECEIVING_DATA) } diff --git a/src/lib.rs b/src/lib.rs index b8db7f1..af51028 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,16 @@ mod types; pub mod constants; pub mod fifo; -pub use client::{Client, ClientError, ClientName, ClientResult, ClientStatus}; +#[cfg(not(feature = "async-mio"))] +pub use client::Client; + +pub use client::{ClientError, ClientName, ClientResult, ClientStatus}; pub use constants::*; pub use types::StatusLine; pub use types::*; + +#[cfg(feature = "async-mio")] +mod async_mio; + +#[cfg(feature = "async-mio")] +pub use async_mio::{AsyncClient, Request, Response}; diff --git a/src/types.rs b/src/types.rs index 344f454..b356c5f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -259,44 +259,61 @@ pub enum EventType { IndexMark(String), } +/// Event identifier +#[derive(Debug)] +pub struct EventId { + // Message id + pub message: String, + // Client id + pub client: String, +} + +impl EventId { + // New event identifier + pub fn new(message: &str, client: &str) -> Self { + Self { + message: message.to_string(), + client: client.to_string(), + } + } +} + /// Notification event #[derive(Debug)] pub struct Event { pub ntype: EventType, - pub message: String, - pub client: String, + pub id: EventId, } impl Event { - pub fn new(ntype: EventType, message: String, client: String) -> Event { + pub fn new(ntype: EventType, message: &str, client: &str) -> Event { Event { ntype, - message, - client, + id: EventId::new(message, client), } } - pub fn begin(message: String, client: String) -> Event { + pub fn begin(message: &str, client: &str) -> Event { Event::new(EventType::Begin, message, client) } - pub fn end(message: String, client: String) -> Event { + pub fn end(message: &str, client: &str) -> Event { Event::new(EventType::End, message, client) } - pub fn index_mark(mark: String, message: String, client: String) -> Event { + pub fn index_mark(mark: String, message: &str, client: &str) -> Event { Event::new(EventType::IndexMark(mark), message, client) } - pub fn cancel(message: String, client: String) -> Event { + pub fn cancel(message: &str, client: &str) -> Event { Event::new(EventType::Cancel, message, client) } - pub fn pause(message: String, client: String) -> Event { + pub fn pause(message: &str, client: &str) -> Event { Event::new(EventType::Pause, message, client) } - pub fn resume(message: String, client: String) -> Event { + pub fn resume(message: &str, client: &str) -> Event { Event::new(EventType::Resume, message, client) } } diff --git a/tests/fifo_sync_tests.rs b/tests/fifo_sync_tests.rs index 790db3c..02577aa 100644 --- a/tests/fifo_sync_tests.rs +++ b/tests/fifo_sync_tests.rs @@ -393,8 +393,7 @@ fn receive_notification() -> io::Result<()> { match client.receive_event() { Ok(Event { ntype: EventType::Begin, - message: _, - client: _, + .. }) => Ok(()), Ok(_) => panic!("wrong event"), Err(_) => panic!("error on event"),