You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

374 lines
15 KiB

// ssip-client -- Speech Dispatcher client in Rust
// Copyright (c) 2022 Laurent Pelecq
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.
use mio::event::Source;
use std::collections::VecDeque;
use std::io::{self, Read, Write};
use crate::{
client::{Client, ClientError, ClientName, ClientResult},
constants::*,
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> {
/// New asynchronous client build on top of a synchronous client.
pub fn new(client: Client<S>) -> Self {
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),
}
}
/// 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()
}
/// 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(()),
}
}
/// 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"),
}
}
}