Move generic methods send and receive from AsyncClient to Client.

* Generic methods can be used in the synchronous API. It gives the choice to use
  either specific calls or the generic ones.
* AsyncClient consists in a simple queue of requests.
* Getters only return the generic Response::Get. Callers must convert if it's an
  integer.
main
Laurent Pelecq 2 years ago
parent fad9440145
commit f662405997

@ -1,6 +1,6 @@
[package]
name = "ssip-client"
version = "0.4.1"
version = "0.5.0"
authors = ["Laurent Pelecq <lpelecq+rust@circoise.eu>"]
edition = "2018"
description = "Client API for Speech Dispatcher"

@ -12,128 +12,18 @@ use std::collections::VecDeque;
use std::io::{self, Read, Write};
use crate::{
client::{Client, ClientError, ClientName, ClientResult},
constants::*,
client::{Client, Request, Response},
types::*,
};
#[derive(Debug, Clone)]
/// Request for SSIP server.
pub enum Request {
SetName(ClientName),
// Speech related requests
Speak,
SendLine(String),
SendLines(Vec<String>),
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)]
/// 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<String>), // 240
HistoryMsgsListSent(Vec<String>), // 241
HistoryLastMsg(String), // 242
HistoryCurPosRet(String), // 243
TableListSent(Vec<String>), // 244
HistoryClientIdSent(String), // 245
MessageTextSent, // 246
HelpSent(Vec<String>), // 248
VoicesListSent(Vec<SynthesisVoice>), // 249
OutputModulesListSent(Vec<String>), // 250
GetString(String), // 251
GetInteger(i8), // 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;
enum GetType {
StringType,
IntegerType,
}
/// Asynchronous client based on `mio`.
///
///
pub struct AsyncClient<S: Read + Write + Source> {
client: Client<S>,
requests: VecDeque<Request>,
get_types: VecDeque<GetType>,
}
impl<S: Read + Write + Source> AsyncClient<S> {
@ -142,16 +32,6 @@ impl<S: Read + Write + Source> AsyncClient<S> {
Self {
client,
requests: VecDeque::with_capacity(INITIAL_REQUEST_QUEUE_CAPACITY),
get_types: VecDeque::with_capacity(INITIAL_REQUEST_QUEUE_CAPACITY),
}
}
/// Convert two lines of the response in an event id
fn parse_event_id(lines: &[String]) -> ClientResult<EventId> {
match lines.len() {
0 | 1 => Err(ClientError::TooFewLines),
2 => Ok(EventId::new(&lines[0], &lines[1])),
_ => Err(ClientError::TooManyLines),
}
}
@ -175,199 +55,21 @@ impl<S: Read + Write + Source> AsyncClient<S> {
!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`.
/// 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::<Vec<&str>>()
.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(()),
if let Some(request) = self.requests.pop_front() {
self.client.send(request)?;
}
Ok(())
}
/// Receive one response.
///
/// Must be called each time a readable event is returned by `mio::Poll`.
pub fn receive_next(&mut self) -> ClientResult<Response> {
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::<S>::parse_single_value(
&lines,
)?)),
OK_CUR_POS_RET => Ok(Response::HistoryCurPosRet(Client::<S>::parse_single_value(
&lines,
)?)),
OK_TABLE_LIST_SENT => Ok(Response::TableListSent(lines)),
OK_CLIENT_ID_SENT => Ok(Response::HistoryClientIdSent(
Client::<S>::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::<S>::parse_synthesis_voices(&lines)?,
)),
OK_OUTPUT_MODULES_LIST_SENT => Ok(Response::OutputModulesListSent(lines)),
OK_GET => {
let sval = Client::<S>::parse_single_value(&lines)?;
match self
.get_types
.pop_front()
.expect("internal error: get_types is empty")
{
GetType::StringType => Ok(Response::GetString(sval)),
GetType::IntegerType => sval
.parse::<i8>()
.map(|uval| Response::GetInteger(uval))
.map_err(|_| ClientError::InvalidType),
}
}
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"),
}
self.client.receive()
}
}

@ -8,15 +8,12 @@
// modified, or distributed except according to those terms.
use std::io::{self, Read, Write};
use std::str::FromStr;
use thiserror::Error as ThisError;
use crate::constants::*;
use crate::protocol::{send_lines, write_lines};
use crate::types::{
CapitalLettersRecognitionMode, ClientScope, Event, KeyName, MessageId, MessageScope,
NotificationType, Priority, PunctuationMode, ReturnCode, StatusLine, SynthesisVoice,
use crate::protocol::{
flush_lines, parse_event_id, parse_single_value, parse_synthesis_voices, write_lines,
};
use crate::types::*;
// Trick to have common implementation for std and mio streams..
#[cfg(not(feature = "async-mio"))]
@ -25,65 +22,6 @@ use std::fmt::Debug as Source;
#[cfg(feature = "async-mio")]
use mio::event::Source;
/// Client error, either I/O error or SSIP error.
#[derive(ThisError, Debug)]
pub enum ClientError {
#[error("Invalid type")]
InvalidType,
#[error("I/O: {0}")]
Io(io::Error),
#[error("Not ready")]
NotReady,
#[error("SSIP: {0}")]
Ssip(StatusLine),
#[error("Too few lines")]
TooFewLines,
#[error("Too many lines")]
TooManyLines,
#[error("Truncated message")]
TruncatedMessage,
#[error("Unexpected status: {0}")]
UnexpectedStatus(ReturnCode),
}
impl From<io::Error> for ClientError {
fn from(err: io::Error) -> Self {
if err.kind() == io::ErrorKind::WouldBlock {
ClientError::NotReady
} else {
ClientError::Io(err)
}
}
}
/// Client result.
pub type ClientResult<T> = Result<T, ClientError>;
/// Client result consisting in a single status line
pub type ClientStatus = ClientResult<StatusLine>;
/// Client name
#[derive(Debug, Clone)]
pub struct ClientName {
pub user: String,
pub application: String,
pub component: String,
}
impl ClientName {
pub fn new(user: &str, application: &str) -> Self {
ClientName::with_component(user, application, "main")
}
pub fn with_component(user: &str, application: &str, component: &str) -> Self {
ClientName {
user: user.to_string(),
application: application.to_string(),
component: component.to_string(),
}
}
}
/// Convert boolean to ON or OFF
fn on_off(value: bool) -> &'static str {
if value {
@ -93,83 +31,142 @@ fn on_off(value: bool) -> &'static str {
}
}
macro_rules! client_send {
($name:ident, $doc:expr, $scope:ident, $value_name:ident as $value_type:ty, $fmt:expr, $value:expr) => {
#[doc=$doc]
pub fn $name(
&mut self,
$scope: ClientScope,
$value_name: $value_type,
) -> ClientResult<&mut Client<S>> {
let line = match $scope {
ClientScope::Current => format!($fmt, "self", $value),
ClientScope::All => format!($fmt, "all", $value),
ClientScope::Client(id) => format!($fmt, id, $value),
};
send_lines(&mut self.output, &[line.as_str()])?;
Ok(self)
}
};
($name:ident, $doc:expr, $scope:ident, $value_name:ident as $value_type:ty, $fmt:expr) => {
client_send!(
$name,
$doc,
$scope,
$value_name as $value_type,
$fmt,
$value_name
);
};
($name:ident, $doc:expr, $value_name:ident as $value_type:ty, $fmt:expr, $value:expr) => {
#[doc=$doc]
pub fn $name(&mut self, $value_name: $value_type) -> ClientResult<&mut Client<S>> {
send_lines(&mut self.output, &[format!($fmt, $value).as_str()])?;
Ok(self)
}
};
($name:ident, $doc:expr, $value_name:ident as $value_type:ty, $fmt:expr) => {
client_send!($name, $doc, $value_name as $value_type, $fmt, $value_name);
};
($name:ident, $doc:expr, $line:expr) => {
#[doc=$doc]
pub fn $name(&mut self) -> ClientResult<&mut Client<S>> {
send_lines(&mut self.output, &[$line])?;
Ok(self)
}
#[derive(Debug, Clone)]
/// Request for SSIP server.
pub enum Request {
SetName(ClientName),
// Speech related requests
Speak,
SendLine(String),
SendLines(Vec<String>),
SpeakChar(char),
SpeakKey(KeyName),
// Flow control
Stop(MessageScope),
Cancel(MessageScope),
Pause(MessageScope),
Resume(MessageScope),
// Setter and getter
SetPriority(Priority),
SetDebug(bool),
SetOutputModule(ClientScope, String),
GetOutputModule,
ListOutputModules,
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),
SetNotification(NotificationType, bool),
Begin,
End,
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<String>), // 240
HistoryMsgsListSent(Vec<String>), // 241
HistoryLastMsg(String), // 242
HistoryCurPosRet(String), // 243
TableListSent(Vec<String>), // 244
HistoryClientIdSent(String), // 245
MessageTextSent, // 246
HelpSent(Vec<String>), // 248
VoicesListSent(Vec<SynthesisVoice>), // 249
OutputModulesListSent(Vec<String>), // 250
Get(String), // 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
}
macro_rules! send_one_line {
($self:expr, $fmt:expr, $( $arg:expr ),+) => {
flush_lines(&mut $self.output, &[format!($fmt, $( $arg ),+).as_str()])
};
($self:expr, $fmt:expr) => {
flush_lines(&mut $self.output, &[$fmt])
}
}
macro_rules! client_send_boolean {
($name:ident, $doc:expr, $scope:ident, $value_name:ident, $fmt:expr) => {
client_send!(
$name,
$doc,
$scope,
$value_name as bool,
$fmt,
on_off($value_name)
);
macro_rules! send_toggle {
($output:expr, $fmt:expr, $val:expr) => {
send_one_line!($output, $fmt, on_off($val))
};
($name:ident, $doc:expr, $value_name:ident, $fmt:expr) => {
client_send!($name, $doc, $value_name as bool, $fmt, on_off($value_name));
($output:expr, $fmt:expr, $arg:expr, $val:expr) => {
send_one_line!($output, $fmt, $arg, on_off($val))
};
}
macro_rules! client_send_range {
($name:ident, $doc:expr, $scope:ident, $value_name:ident, $fmt:expr) => {
client_send!(
$name,
$doc,
$scope,
$value_name as i8,
macro_rules! send_range {
($output:expr, $fmt:expr, $scope:expr, $val:expr) => {
send_one_line!(
$output,
$fmt,
std::cmp::max(-100, std::cmp::min(100, $value_name))
);
$scope,
std::cmp::max(-100, std::cmp::min(100, $val))
)
};
}
/// SSIP client on generic stream
///
/// There are two ways to send requests and receive responses:
/// * Either with the generic [`send`] and [`receive`]
/// * Or with the specific methods such as [`set_rate`], ..., [`get_rate`], ...
pub struct Client<S: Read + Write + Source> {
input: io::BufReader<S>,
output: io::BufWriter<S>,
@ -182,284 +179,381 @@ impl<S: Read + Write + Source> Client<S> {
Self { input, output }
}
/// Return the only string in the list or an error if there is no line or too many.
pub(crate) fn parse_single_value(lines: &[String]) -> ClientResult<String> {
match lines.len() {
0 => Err(ClientError::TooFewLines),
1 => Ok(lines[0].to_string()),
_ => Err(ClientError::TooManyLines),
}
}
pub(crate) fn parse_synthesis_voices(lines: &[String]) -> ClientResult<Vec<SynthesisVoice>> {
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 Self> {
send_lines(
/// Send lines of text (terminated by a single dot).
pub fn send_lines(&mut self, lines: &[String]) -> ClientResult<&mut Self> {
const END_OF_DATA: [&str; 1] = ["."];
write_lines(
&mut self.output,
&[format!(
"SET self CLIENT_NAME {}:{}:{}",
client_name.user, client_name.application, client_name.component
)
.as_str()],
lines
.iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>()
.as_slice(),
)?;
flush_lines(&mut self.output, &END_OF_DATA)?;
Ok(self)
}
/// Initiate communitation to send text to speak
pub fn speak(&mut self) -> ClientResult<&mut Self> {
send_lines(&mut self.output, &["SPEAK"])?;
/// Send one line of text (terminated by a single dot).
pub fn send_line(&mut self, line: &str) -> ClientResult<&mut Self> {
const END_OF_DATA: &str = ".";
flush_lines(&mut self.output, &[line, END_OF_DATA])?;
Ok(self)
}
/// Send lines
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)?;
/// Send a request
pub fn send(&mut self, request: Request) -> ClientResult<&mut Self> {
match request {
Request::SetName(client_name) => send_one_line!(
self,
"SET self CLIENT_NAME {}:{}:{}",
client_name.user,
client_name.application,
client_name.component
),
Request::Speak => send_one_line!(self, "SPEAK"),
Request::SendLine(line) => self.send_line(&line).map(|_| ()),
Request::SendLines(lines) => self.send_lines(&lines).map(|_| ()),
Request::SpeakChar(ch) => send_one_line!(self, "CHAR {}", ch),
Request::SpeakKey(key) => send_one_line!(self, "KEY {}", key),
Request::Stop(scope) => send_one_line!(self, "STOP {}", scope),
Request::Cancel(scope) => send_one_line!(self, "CANCEL {}", scope),
Request::Pause(scope) => send_one_line!(self, "PAUSE {}", scope),
Request::Resume(scope) => send_one_line!(self, "RESUME {}", scope),
Request::SetPriority(prio) => send_one_line!(self, "SET self PRIORITY {}", prio),
Request::SetDebug(value) => send_toggle!(self, "SET all DEBUG {}", value),
Request::SetOutputModule(scope, value) => {
send_one_line!(self, "SET {} OUTPUT_MODULE {}", scope, value)
}
Request::GetOutputModule => send_one_line!(self, "GET OUTPUT_MODULE"),
Request::ListOutputModules => send_one_line!(self, "LIST OUTPUT_MODULES"),
Request::SetLanguage(scope, lang) => {
send_one_line!(self, "SET {} LANGUAGE {}", scope, lang)
}
Request::GetLanguage => send_one_line!(self, "GET LANGUAGE"),
Request::SetSsmlMode(value) => send_toggle!(self, "SET self SSML_MODE {}", value),
Request::SetPunctuationMode(scope, mode) => {
send_one_line!(self, "SET {} PUNCTUATION {}", scope, mode)
}
Request::SetSpelling(scope, value) => {
send_toggle!(self, "SET {} SPELLING {}", scope, value)
}
Request::SetCapitalLettersRecognitionMode(scope, mode) => {
send_one_line!(self, "SET {} CAP_LET_RECOGN {}", scope, mode)
}
Request::SetVoiceType(scope, value) => {
send_one_line!(self, "SET {} VOICE_TYPE {}", scope, value)
}
Request::GetVoiceType => send_one_line!(self, "GET VOICE_TYPE"),
Request::ListVoiceTypes => send_one_line!(self, "LIST VOICES"),
Request::SetSynthesisVoice(scope, value) => {
send_one_line!(self, "SET {} SYNTHESIS_VOICE {}", scope, value)
}
Request::ListSynthesisVoices => send_one_line!(self, "LIST SYNTHESIS_VOICES"),
Request::SetRate(scope, value) => send_range!(self, "SET {} RATE {}", scope, value),
Request::GetRate => send_one_line!(self, "GET RATE"),
Request::SetPitch(scope, value) => send_range!(self, "SET {} PITCH {}", scope, value),
Request::GetPitch => send_one_line!(self, "GET PITCH"),
Request::SetVolume(scope, value) => {
send_range!(self, "SET {} VOLUME {}", scope, value)
}
Request::GetVolume => send_one_line!(self, "GET VOLUME"),
Request::SetPauseContext(scope, value) => {
send_one_line!(self, "SET {} PAUSE_CONTEXT {}", scope, value)
}
Request::SetHistory(scope, value) => {
send_toggle!(self, "SET {} HISTORY {}", scope, value)
}
Request::SetNotification(ntype, value) => {
send_toggle!(self, "SET self NOTIFICATION {} {}", ntype, value)
}
Request::Begin => send_one_line!(self, "BLOCK BEGIN"),
Request::End => send_one_line!(self, "BLOCK END"),
Request::Quit => send_one_line!(self, "QUIT"),
}?;
Ok(self)
}
/// Send a line
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)
/// Set the client name. It must be the first call on startup.
pub fn set_client_name(&mut self, client_name: ClientName) -> ClientResult<&mut Self> {
self.send(Request::SetName(client_name))
}
/// Send a char
pub fn send_char(&mut self, ch: char) -> ClientResult<&mut Self> {
send_lines(&mut self.output, &[format!("CHAR {}", ch).as_str()])?;
Ok(self)
/// Initiate communitation to send text to speak
pub fn speak(&mut self) -> ClientResult<&mut Self> {
self.send(Request::Speak)
}
/// Send a symbolic key name
pub fn say_key_name(&mut self, keyname: KeyName) -> ClientResult<&mut Self> {
self.send_lines(&[format!("KEY {}", keyname).as_str()])
/// Speak a char
pub fn speak_char(&mut self, ch: char) -> ClientResult<&mut Self> {
self.send(Request::SpeakChar(ch))
}
/// Action on a message or a group of messages
fn send_message_command(
&mut self,
command: &str,
scope: MessageScope,
) -> ClientResult<&mut Self> {
let line = match scope {
MessageScope::Last => format!("{} self", command),
MessageScope::All => format!("{} all", command),
MessageScope::Message(id) => format!("{} {}", command, id),
};
send_lines(&mut self.output, &[line.as_str()])?;
Ok(self)
/// Speak a symbolic key name
pub fn speak_key(&mut self, key_name: KeyName) -> ClientResult<&mut Self> {
self.send(Request::SpeakKey(key_name))
}
/// Stop current message
pub fn stop(&mut self, scope: MessageScope) -> ClientResult<&mut Self> {
self.send_message_command("STOP", scope)
self.send(Request::Stop(scope))
}
/// Cancel current message
pub fn cancel(&mut self, scope: MessageScope) -> ClientResult<&mut Self> {
self.send_message_command("CANCEL", scope)
self.send(Request::Cancel(scope))
}
/// Pause current message
pub fn pause(&mut self, scope: MessageScope) -> ClientResult<&mut Self> {
self.send_message_command("PAUSE", scope)
self.send(Request::Pause(scope))
}
/// Resume current message
pub fn resume(&mut self, scope: MessageScope) -> ClientResult<&mut Self> {
self.send_message_command("RESUME", scope)
}
client_send!(
set_priority,
"Set message priority",
priority as Priority,
"SET self PRIORITY {}"
);
client_send_boolean!(
set_debug,
"Set debug mode. Return the log location",
value,
"SET all DEBUG {}"
);
client_send!(
set_output_module,
"Set output module",
scope,
value as &str,
"SET {} OUTPUT_MODULE {}"
);
client_send!(
get_output_module,
"Get the current output module",
"GET OUTPUT_MODULE"
);
client_send!(
list_output_modules,
"List the available output modules",
"LIST OUTPUT_MODULES"
);
client_send!(
set_language,
"Set language code",
scope,
value as &str,
"SET {} LANGUAGE {}"
);
client_send!(get_language, "Get the current language", "GET LANGUAGE");
client_send_boolean!(
set_ssml_mode,
"Set SSML mode (Speech Synthesis Markup Language)",
value,
"SET self SSML_MODE {}"
);
client_send!(
set_punctuation_mode,
"Set punctuation mode",
scope,
value as PunctuationMode,
"SET {} PUNCTUATION {}"
);
client_send_boolean!(
set_spelling,
"Set spelling on or off",
scope,
value,
"SET {} SPELLING {}"
);
client_send!(
set_capital_letter_recogn,
"Set capital letters recognition mode",
scope,
value as CapitalLettersRecognitionMode,
"SET {} CAP_LET_RECOGN {}"
);
client_send!(
set_voice_type,
"Set the voice type (MALE1, FEMALE1, …)",
scope,
value as &str,
"SET {} VOICE_TYPE {}"
);
client_send!(
get_voice_type,
"Get the current pre-defined voice",
"GET VOICE_TYPE"
);
client_send!(
list_voice_types,
"List the available symbolic voice names",
"LIST VOICES"
);
client_send!(
set_synthesis_voice,
"Set the voice",
scope,
value as &str,
"SET {} SYNTHESIS_VOICE {}"
);
client_send!(
list_synthesis_voices,
"Lists the available voices for the current synthesizer",
"LIST SYNTHESIS_VOICES"
);
client_send_range!(
set_rate,
"Set the rate of speech. n is an integer value within the range from -100 to 100, lower values meaning slower speech.",
scope,
value,
"SET {} RATE {}"
);
client_send!(get_rate, "Get the current rate of speech.", "GET RATE");
client_send_range!(
set_pitch,
"Set the pitch of speech. n is an integer value within the range from -100 to 100.",
scope,
value,
"SET {} PITCH {}"
);
client_send!(get_pitch, "Get the current pitch value.", "GET PITCH");
client_send_range!(
set_volume,
"Set the volume of speech. n is an integer value within the range from -100 to 100.",
scope,
value,
"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.",
scope,
value as u8,
"SET {} PAUSE_CONTEXT {}"
);
client_send_boolean!(
set_history,
"Enable or disable history of received messages.",
scope,
value,
"SET {} HISTORY {}"
);
client_send!(block_begin, "Open a block", "BLOCK BEGIN");
client_send!(block_end, "End a block", "BLOCK END");
client_send!(quit, "Close the connection", "QUIT");
client_send!(
enable_notification,
"Enable notification events",
value as NotificationType,
"SET self NOTIFICATION {} on"
);
client_send!(
disable_notification,
"Disable notification events",
value as NotificationType,
"SET self NOTIFICATION {} off"
);
self.send(Request::Resume(scope))
}
/// Set message priority
pub fn set_priority(&mut self, prio: Priority) -> ClientResult<&mut Self> {
self.send(Request::SetPriority(prio))
}
/// Set debug mode. Return the log location
pub fn set_debug(&mut self, value: bool) -> ClientResult<&mut Self> {
self.send(Request::SetDebug(value))
}
/// Set output module
pub fn set_output_module(
&mut self,
scope: ClientScope,
value: &str,
) -> ClientResult<&mut Self> {
self.send(Request::SetOutputModule(scope, value.to_string()))
}
/// Get the current output module
pub fn get_output_module(&mut self) -> ClientResult<&mut Self> {
self.send(Request::GetOutputModule)
}
/// List the available output modules
pub fn list_output_modules(&mut self) -> ClientResult<&mut Self> {
self.send(Request::ListOutputModules)
}
/// Set language code
pub fn set_language(&mut self, scope: ClientScope, value: &str) -> ClientResult<&mut Self> {
self.send(Request::SetLanguage(scope, value.to_string()))
}
/// Get the current language
pub fn get_language(&mut self) -> ClientResult<&mut Self> {
self.send(Request::GetLanguage)
}
/// Set SSML mode (Speech Synthesis Markup Language)
pub fn set_ssml_mode(&mut self, mode: bool) -> ClientResult<&mut Self> {
self.send(Request::SetSsmlMode(mode))
}
/// Set punctuation mode
pub fn set_punctuation_mode(
&mut self,
scope: ClientScope,
mode: PunctuationMode,
) -> ClientResult<&mut Self> {
self.send(Request::SetPunctuationMode(scope, mode))
}
/// Set spelling on or off
pub fn set_spelling(&mut self, scope: ClientScope, value: bool) -> ClientResult<&mut Self> {
self.send(Request::SetSpelling(scope, value))
}
/// Set capital letters recognition mode
pub fn set_capital_letter_recogn(
&mut self,
scope: ClientScope,
mode: CapitalLettersRecognitionMode,
) -> ClientResult<&mut Self> {
self.send(Request::SetCapitalLettersRecognitionMode(scope, mode))
}
/// Set the voice type (MALE1, FEMALE1, …)
pub fn set_voice_type(&mut self, scope: ClientScope, value: &str) -> ClientResult<&mut Self> {
self.send(Request::SetVoiceType(scope, value.to_string()))
}
/// Get the current pre-defined voice
pub fn get_voice_type(&mut self) -> ClientResult<&mut Self> {
self.send(Request::GetVoiceType)
}
/// List the available symbolic voice names
pub fn list_voice_types(&mut self) -> ClientResult<&mut Self> {
self.send(Request::ListVoiceTypes)
}
/// Set the voice
pub fn set_synthesis_voice(
&mut self,
scope: ClientScope,
value: &str,
) -> ClientResult<&mut Self> {
self.send(Request::SetSynthesisVoice(scope, value.to_string()))
}
/// Lists the available voices for the current synthesizer
pub fn list_synthesis_voices(&mut self) -> ClientResult<&mut Self> {
self.send(Request::ListSynthesisVoices)
}
/// Set the rate of speech. n is an integer value within the range from -100 to 100, lower values meaning slower speech.
pub fn set_rate(&mut self, scope: ClientScope, value: i8) -> ClientResult<&mut Self> {
self.send(Request::SetRate(scope, value))
}
/// Get the current rate of speech.
pub fn get_rate(&mut self) -> ClientResult<&mut Self> {
self.send(Request::GetRate)
}
/// Set the pitch of speech. n is an integer value within the range from -100 to 100.
pub fn set_pitch(&mut self, scope: ClientScope, value: i8) -> ClientResult<&mut Self> {
self.send(Request::SetPitch(scope, value))
}
/// Get the current pitch value.
pub fn get_pitch(&mut self) -> ClientResult<&mut Self> {
self.send(Request::GetPitch)
}
/// Set the volume of speech. n is an integer value within the range from -100 to 100.
pub fn set_volume(&mut self, scope: ClientScope, value: i8) -> ClientResult<&mut Self> {
self.send(Request::SetVolume(scope, value))
}
/// Get the current volume.
pub fn get_volume(&mut self) -> ClientResult<&mut Self> {
self.send(Request::GetVolume)
}
/// Set the number of (more or less) sentences that should be repeated after a previously paused text is resumed.
pub fn set_pause_context(&mut self, scope: ClientScope, value: u8) -> ClientResult<&mut Self> {
self.send(Request::SetPauseContext(scope, value))
}
/// Enable or disable history of received messages.
pub fn set_history(&mut self, scope: ClientScope, value: bool) -> ClientResult<&mut Self> {
self.send(Request::SetHistory(scope, value))
}
/// Enable notification events
pub fn set_notification(
&mut self,
ntype: NotificationType,
value: bool,
) -> ClientResult<&mut Self> {
self.send(Request::SetNotification(ntype, value))
}
/// Open a block
pub fn block_begin(&mut self) -> ClientResult<&mut Self> {
self.send(Request::Begin)
}
/// End a block
pub fn block_end(&mut self) -> ClientResult<&mut Self> {
self.send(Request::End)
}
/// Close the connection
pub fn quit(&mut self) -> ClientResult<&mut Self> {
self.send(Request::Quit)
}
/// Receive answer from server
pub fn receive(&mut self, lines: &mut Vec<String>) -> ClientStatus {
fn receive_answer(&mut self, lines: &mut Vec<String>) -> ClientStatus {
crate::protocol::receive_answer(&mut self.input, Some(lines))
}
/// Receive one response.
pub fn receive(&mut self) -> ClientResult<Response> {
const MSG_CURSOR_SET_FIRST: &str = "OK CURSOR SET FIRST";
let mut lines = Vec::new();
let status = self.receive_answer(&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(parse_single_value(&lines)?)),
OK_CUR_POS_RET => Ok(Response::HistoryCurPosRet(parse_single_value(&lines)?)),
OK_TABLE_LIST_SENT => Ok(Response::TableListSent(lines)),
OK_CLIENT_ID_SENT => Ok(Response::HistoryClientIdSent(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(parse_synthesis_voices(&lines)?)),
OK_OUTPUT_MODULES_LIST_SENT => Ok(Response::OutputModulesListSent(lines)),
OK_GET => Ok(Response::Get(parse_single_value(&lines)?)),
OK_INSIDE_BLOCK => Ok(Response::InsideBlock),
OK_OUTSIDE_BLOCK => Ok(Response::OutsideBlock),
OK_NOT_IMPLEMENTED => Ok(Response::NotImplemented),
EVENT_INDEX_MARK => match lines.len() {
0 | 1 | 2 => Err(ClientError::TooFewLines),
3 => Ok(Response::EventIndexMark(
parse_event_id(&lines)?,
lines[2].to_owned(),
)),
_ => Err(ClientError::TooManyLines),
},
EVENT_BEGIN => Ok(Response::EventBegin(parse_event_id(&lines)?)),
EVENT_END => Ok(Response::EventEnd(parse_event_id(&lines)?)),
EVENT_CANCELED => Ok(Response::EventCanceled(parse_event_id(&lines)?)),
EVENT_PAUSED => Ok(Response::EventPaused(parse_event_id(&lines)?)),
EVENT_RESUMED => Ok(Response::EventResumed(parse_event_id(&lines)?)),
_ => panic!("error should have been caught earlier"),
}
}
/// Check status of answer, discard lines.
pub fn check_status(&mut self, expected_code: ReturnCode) -> ClientResult<&mut Self> {
crate::protocol::receive_answer(&mut self.input, None).and_then(|status| {
@ -474,7 +568,7 @@ impl<S: Read + Write + Source> Client<S> {
/// Receive lines
pub fn receive_lines(&mut self, expected_code: ReturnCode) -> ClientResult<Vec<String>> {
let mut lines = Vec::new();
let status = self.receive(&mut lines)?;
let status = self.receive_answer(&mut lines)?;
if status.code == expected_code {
Ok(lines)
} else {
@ -485,7 +579,7 @@ impl<S: Read + Write + Source> Client<S> {
/// Receive a single string
pub fn receive_string(&mut self, expected_code: ReturnCode) -> ClientResult<String> {
self.receive_lines(expected_code)
.and_then(|lines| Self::parse_single_value(&lines))
.and_then(|lines| parse_single_value(&lines))
}
/// Receive integer
@ -503,7 +597,7 @@ impl<S: Read + Write + Source> Client<S> {
/// Receive a list of synthesis voices
pub fn receive_synthesis_voices(&mut self) -> ClientResult<Vec<SynthesisVoice>> {
self.receive_lines(OK_VOICES_LIST_SENT)
.and_then(|lines| Self::parse_synthesis_voices(&lines))
.and_then(|lines| parse_synthesis_voices(&lines))
}
/// Receive a notification
@ -560,26 +654,3 @@ impl<S: Read + Write + Source> Client<S> {
Ok(())
}
}
#[cfg(test)]
mod tests {
#[cfg(not(feature = "async-mio"))]
use std::net::TcpStream;
#[cfg(feature = "async-mio")]
use mio::net::TcpStream;
use super::{Client, ClientError};
#[test]
fn parse_single_value() {
let result = Client::<TcpStream>::parse_single_value(&[String::from("one")]).unwrap();
assert_eq!("one", result);
let err_empty = Client::<TcpStream>::parse_single_value(&[]);
assert!(matches!(err_empty, Err(ClientError::TooFewLines)));
let err_too_many =
Client::<TcpStream>::parse_single_value(&[String::from("one"), String::from("two")]);
assert!(matches!(err_too_many, Err(ClientError::TooManyLines)));
}
}

@ -38,7 +38,7 @@ pub mod fifo;
#[cfg(any(not(feature = "async-mio"), doc))]
pub use client::Client;
pub use client::{ClientError, ClientName, ClientResult, ClientStatus};
pub use client::{Request, Response};
pub use constants::*;
pub use types::*;
@ -46,4 +46,4 @@ pub use types::*;
mod async_mio;
#[cfg(feature = "async-mio")]
pub use async_mio::{AsyncClient, Request, Response};
pub use async_mio::AsyncClient;

@ -9,9 +9,9 @@
use log::debug;
use std::io::{self, BufRead, Write};
use std::str::FromStr;
use crate::client::{ClientError, ClientResult, ClientStatus};
use crate::types::StatusLine;
use crate::types::{ClientError, ClientResult, ClientStatus, EventId, StatusLine, SynthesisVoice};
macro_rules! invalid_input {
($msg:expr) => {
@ -22,6 +22,33 @@ macro_rules! invalid_input {
};
}
/// Return the only string in the list or an error if there is no line or too many.
pub(crate) fn parse_single_value(lines: &[String]) -> ClientResult<String> {
match lines.len() {
0 => Err(ClientError::TooFewLines),
1 => Ok(lines[0].to_string()),
_ => Err(ClientError::TooManyLines),
}
}
/// Convert two lines of the response in an event id
pub(crate) fn parse_event_id(lines: &[String]) -> ClientResult<EventId> {
match lines.len() {
0 | 1 => Err(ClientError::TooFewLines),
2 => Ok(EventId::new(&lines[0], &lines[1])),
_ => Err(ClientError::TooManyLines),
}
}
pub(crate) fn parse_synthesis_voices(lines: &[String]) -> ClientResult<Vec<SynthesisVoice>> {
let mut voices = Vec::new();
for name in lines.iter() {
let voice = SynthesisVoice::from_str(name.as_str())?;
voices.push(voice);
}
Ok(voices)
}
/// Write lines separated by CRLF.
pub(crate) fn write_lines(output: &mut dyn Write, lines: &[&str]) -> ClientResult<()> {
for line in lines.iter() {
@ -33,7 +60,7 @@ pub(crate) fn write_lines(output: &mut dyn Write, lines: &[&str]) -> ClientResul
}
/// Write lines separated by CRLF and flush the output.
pub(crate) fn send_lines(output: &mut dyn Write, lines: &[&str]) -> ClientResult<()> {
pub(crate) fn flush_lines(output: &mut dyn Write, lines: &[&str]) -> ClientResult<()> {
write_lines(output, lines)?;
output.flush()?;
Ok(())
@ -91,7 +118,7 @@ mod tests {
use std::io::BufReader;
use super::{receive_answer, ClientError};
use super::{receive_answer, ClientError, ClientResult};
#[test]
fn single_ok_status_line() {
@ -136,4 +163,73 @@ mod tests {
lines.as_slice()
);
}
#[test]
fn parse_single_value() -> ClientResult<()> {
let no_lines = Vec::new();
assert!(matches!(
super::parse_single_value(&no_lines),
Err(ClientError::TooFewLines)
));
let one = String::from("one");
let one_line = vec![one.to_owned()];
assert_eq!(one, super::parse_single_value(&one_line)?);
let two_lines = vec![one.to_owned(), String::from("two")];
assert!(matches!(
super::parse_single_value(&two_lines),
Err(ClientError::TooManyLines)
));
Ok(())
}
#[test]
fn parse_event_id() -> ClientResult<()> {
let no_lines = Vec::new();
assert!(matches!(
super::parse_event_id(&no_lines),
Err(ClientError::TooFewLines)
));
let one_line = vec![String::from("one")];
assert!(matches!(
super::parse_event_id(&one_line),
Err(ClientError::TooFewLines)
));
let mid = String::from("message");
let cid = String::from("client");
let two_lines = vec![mid.to_owned(), cid.to_owned()];
let event_id = super::parse_event_id(&two_lines)?;
assert_eq!(mid, event_id.message);
assert_eq!(cid, event_id.client);
let three_lines = vec![
String::from("one"),
String::from("two"),
String::from("three"),
];
assert!(matches!(
super::parse_event_id(&three_lines),
Err(ClientError::TooManyLines)
));
Ok(())
}
#[test]
fn parse_synthesis_voices() -> ClientResult<()> {
let lines = ["en", "afrikaans\taf", "lancashire\ten\tuk-north"]
.iter()
.map(|s| s.to_string())
.collect::<Vec<String>>();
let voices = super::parse_synthesis_voices(&lines)?;
assert_eq!(3, voices.len());
assert_eq!("en", voices[0].name.as_str());
assert_eq!(Some(String::from("af")), voices[1].language);
assert_eq!(Some(String::from("uk-north")), voices[2].dialect);
Ok(())
}
}

@ -8,7 +8,9 @@
// modified, or distributed except according to those terms.
use std::fmt;
use std::io;
use std::str::FromStr;
use thiserror::Error as ThisError;
use strum_macros::Display as StrumDisplay;
@ -32,6 +34,16 @@ pub enum MessageScope {
Message(MessageId),
}
impl fmt::Display for MessageScope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MessageScope::Last => write!(f, "self"),
MessageScope::All => write!(f, "all"),
MessageScope::Message(id) => write!(f, "{}", id),
}
}
}
/// Client identifiers
#[derive(Debug, Clone)]
pub enum ClientScope {
@ -43,6 +55,16 @@ pub enum ClientScope {
Client(ClientId),
}
impl fmt::Display for ClientScope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ClientScope::Current => write!(f, "self"),
ClientScope::All => write!(f, "all"),
ClientScope::Client(id) => write!(f, "{}", id),
}
}
}
/// Priority
#[derive(StrumDisplay, Debug, Clone)]
pub enum Priority {
@ -347,7 +369,7 @@ impl SynthesisVoice {
}
impl FromStr for SynthesisVoice {
type Err = std::io::Error;
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut iter = s.split('\t');
@ -377,13 +399,71 @@ impl fmt::Display for StatusLine {
write!(f, "{} {}", self.code, self.message)
}
}
/// Client error, either I/O error or SSIP error.
#[derive(ThisError, Debug)]
pub enum ClientError {
#[error("Invalid type")]
InvalidType,
#[error("I/O: {0}")]
Io(io::Error),
#[error("Not ready")]
NotReady,
#[error("SSIP: {0}")]
Ssip(StatusLine),
#[error("Too few lines")]
TooFewLines,
#[error("Too many lines")]
TooManyLines,
#[error("Truncated message")]
TruncatedMessage,
#[error("Unexpected status: {0}")]
UnexpectedStatus(ReturnCode),
}
impl From<io::Error> for ClientError {
fn from(err: io::Error) -> Self {
if err.kind() == io::ErrorKind::WouldBlock {
ClientError::NotReady
} else {
ClientError::Io(err)
}
}
}
/// Client result.
pub type ClientResult<T> = Result<T, ClientError>;
/// Client result consisting in a single status line
pub type ClientStatus = ClientResult<StatusLine>;
/// Client name
#[derive(Debug, Clone)]
pub struct ClientName {
pub user: String,
pub application: String,
pub component: String,
}
impl ClientName {
pub fn new(user: &str, application: &str) -> Self {
ClientName::with_component(user, application, "main")
}
pub fn with_component(user: &str, application: &str, component: &str) -> Self {
ClientName {
user: user.to_string(),
application: application.to_string(),
component: component.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::SynthesisVoice;
use super::{MessageScope, SynthesisVoice};
#[test]
fn parse_synthesis_voice() {
@ -400,4 +480,14 @@ mod tests {
assert_eq!("eo", v2.language.unwrap());
assert!(matches!(v2.dialect, None));
}
#[test]
fn format_message_scope() {
assert_eq!("self", format!("{}", MessageScope::Last).as_str());
assert_eq!("all", format!("{}", MessageScope::All).as_str());
assert_eq!(
"123",
format!("{}", MessageScope::Message("123".to_string())).as_str()
);
}
}

@ -20,12 +20,6 @@ mod server;
#[cfg(feature = "async-mio")]
use server::Server;
#[cfg(feature = "async-mio")]
enum Answer {
Str(&'static str),
Int(i8),
}
#[cfg(feature = "async-mio")]
struct State<'a, 'b> {
pub done: bool,
@ -33,12 +27,12 @@ struct State<'a, 'b> {
pub writable: bool,
pub start_get: bool,
pub iter_requests: Iter<'a, Request>,
pub iter_answers: Iter<'b, Answer>,
pub iter_answers: Iter<'b, &'static str>,
}
#[cfg(feature = "async-mio")]
impl<'a, 'b> State<'a, 'b> {
fn new(iter_requests: Iter<'a, Request>, iter_answers: Iter<'b, Answer>) -> Self {
fn new(iter_requests: Iter<'a, Request>, iter_answers: Iter<'b, &'static str>) -> Self {
State {
done: false,
countdown: 50,
@ -65,24 +59,9 @@ impl<'a, 'b> State<'a, 'b> {
}
}
fn assert_string(&mut self, val: &str) {
match self.iter_answers.next() {
Some(Answer::Str(expected_val)) => assert_eq!(expected_val, &val),
Some(Answer::Int(expected_val)) => panic!(
"expecting integer {} instead of string '{}'",
expected_val, val
),
None => panic!("no more answers"),
}
}
fn assert_integer(&mut self, val: i8) {
fn assert_answer(&mut self, val: &str) {
match self.iter_answers.next() {
Some(Answer::Int(expected_val)) => assert_eq!(expected_val, &val),
Some(Answer::Str(expected_val)) => panic!(
"expecting string '{}' instead of integer {}",
expected_val, val
),
Some(expected_val) => assert_eq!(expected_val, &val),
None => panic!("no more answers"),
}
}
@ -106,7 +85,7 @@ fn basic_async_communication() -> ClientResult<()> {
];
let get_requests = vec![Request::GetOutputModule, Request::GetRate];
let get_answers = vec![Answer::Str("espeak"), Answer::Int(10)];
let get_answers = vec!["espeak", "10"];
let mut state = State::new(get_requests.iter(), get_answers.iter());
let socket_dir = tempfile::tempdir()?;
@ -135,8 +114,7 @@ fn basic_async_communication() -> ClientResult<()> {
}
Response::LanguageSet => client.push(Request::Stop(MessageScope::Last)),
Response::Stopped => state.start_get = true,
Response::GetString(val) => state.assert_string(&val),
Response::GetInteger(val) => state.assert_integer(val),
Response::Get(val) => state.assert_answer(&val),
result => panic!("Unexpected response: {:?}", result),
}
if let Some(request) = state.next_request() {

Loading…
Cancel
Save