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.
851 lines
33 KiB
851 lines
33 KiB
// ssip-client -- Speech Dispatcher client in Rust
|
|
// Copyright (c) 2021-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 std::io::{self, Read, Write};
|
|
|
|
use crate::constants::*;
|
|
use crate::protocol::{
|
|
flush_lines, parse_event_id, parse_single_integer, parse_single_value, parse_typed_lines,
|
|
write_lines,
|
|
};
|
|
use crate::types::*;
|
|
|
|
// Trick to have common implementation for std and mio streams..
|
|
#[cfg(all(not(feature = "async-mio"), unix))]
|
|
pub use std::os::unix::io::AsRawFd as Source;
|
|
|
|
#[cfg(feature = "async-mio")]
|
|
pub use mio::event::Source;
|
|
|
|
/// Convert boolean to ON or OFF
|
|
fn on_off(value: bool) -> &'static str {
|
|
if value {
|
|
"on"
|
|
} else {
|
|
"off"
|
|
}
|
|
}
|
|
|
|
#[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, u32),
|
|
SetNotification(NotificationType, bool),
|
|
// Blocks
|
|
Begin,
|
|
End,
|
|
// History
|
|
SetHistory(ClientScope, bool),
|
|
HistoryGetClients,
|
|
HistoryGetClientId,
|
|
HistoryGetClientMsgs(ClientScope, u32, u32),
|
|
HistoryGetLastMsgId,
|
|
HistoryGetMsg(MessageId),
|
|
HistoryCursorGet,
|
|
HistoryCursorSet(ClientScope, HistoryPosition),
|
|
HistoryCursorMove(CursorDirection),
|
|
HistorySpeak(MessageId),
|
|
HistorySort(SortDirection, SortKey),
|
|
HistorySetShortMsgLength(u32),
|
|
HistorySetMsgTypeOrdering(Vec<Ordering>),
|
|
HistorySearch(ClientScope, String),
|
|
// Misc.
|
|
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<HistoryClientStatus>), // 240
|
|
HistoryMsgsListSent(Vec<String>), // 241
|
|
HistoryLastMsg(String), // 242
|
|
HistoryCurPosRet(String), // 243
|
|
TableListSent(Vec<String>), // 244
|
|
HistoryClientIdSent(ClientId), // 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! send_toggle {
|
|
($output:expr, $fmt:expr, $val:expr) => {
|
|
send_one_line!($output, $fmt, on_off($val))
|
|
};
|
|
($output:expr, $fmt:expr, $arg:expr, $val:expr) => {
|
|
send_one_line!($output, $fmt, $arg, on_off($val))
|
|
};
|
|
}
|
|
|
|
macro_rules! send_range {
|
|
($output:expr, $fmt:expr, $scope:expr, $val:expr) => {
|
|
send_one_line!(
|
|
$output,
|
|
$fmt,
|
|
$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 [`Client::send`] and [`Client::receive`]
|
|
/// * Or with the specific methods such as [`Client::set_rate`], ..., [`Client::get_rate`], ...
|
|
pub struct Client<S: Read + Write + Source> {
|
|
input: io::BufReader<S>,
|
|
output: io::BufWriter<S>,
|
|
}
|
|
|
|
impl<S: Read + Write + Source> Client<S> {
|
|
/// Create a SSIP client on the reader and writer.
|
|
pub(crate) fn new(input: io::BufReader<S>, output: io::BufWriter<S>) -> Self {
|
|
// https://stackoverflow.com/questions/58467659/how-to-store-tcpstream-with-bufreader-and-bufwriter-in-a-data-structure
|
|
Self { input, output }
|
|
}
|
|
|
|
#[cfg(all(not(feature = "async-mio"), unix))]
|
|
/// Input source for asynchronous API based on `poll`.
|
|
pub(crate) fn input_source(&self) -> &S {
|
|
self.input.get_ref()
|
|
}
|
|
|
|
#[cfg(all(not(feature = "async-mio"), unix))]
|
|
/// Output source for asynchronous API based on `poll`.
|
|
pub(crate) fn output_source(&self) -> &S {
|
|
self.output.get_ref()
|
|
}
|
|
|
|
/// 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,
|
|
lines
|
|
.iter()
|
|
.map(|s| s.as_str())
|
|
.collect::<Vec<&str>>()
|
|
.as_slice(),
|
|
)?;
|
|
flush_lines(&mut self.output, &END_OF_DATA)?;
|
|
Ok(self)
|
|
}
|
|
|
|
/// 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 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::HistoryGetClients => send_one_line!(self, "HISTORY GET CLIENT_LIST"),
|
|
Request::HistoryGetClientId => send_one_line!(self, "HISTORY GET CLIENT_ID"),
|
|
Request::HistoryGetClientMsgs(scope, start, number) => send_one_line!(
|
|
self,
|
|
"HISTORY GET CLIENT_MESSAGES {} {}_{}",
|
|
scope,
|
|
start,
|
|
number
|
|
),
|
|
Request::HistoryGetLastMsgId => send_one_line!(self, "HISTORY GET LAST"),
|
|
Request::HistoryGetMsg(id) => send_one_line!(self, "HISTORY GET MESSAGE {}", id),
|
|
Request::HistoryCursorGet => send_one_line!(self, "HISTORY CURSOR GET"),
|
|
Request::HistoryCursorSet(scope, pos) => {
|
|
send_one_line!(self, "HISTORY CURSOR SET {} {}", scope, pos)
|
|
}
|
|
Request::HistoryCursorMove(direction) => {
|
|
send_one_line!(self, "HISTORY CURSOR {}", direction)
|
|
}
|
|
Request::HistorySpeak(id) => send_one_line!(self, "HISTORY SAY {}", id),
|
|
Request::HistorySort(direction, key) => {
|
|
send_one_line!(self, "HISTORY SORT {} {}", direction, key)
|
|
}
|
|
Request::HistorySetShortMsgLength(length) => {
|
|
send_one_line!(self, "HISTORY SET SHORT_MESSAGE_LENGTH {}", length)
|
|
}
|
|
Request::HistorySetMsgTypeOrdering(ordering) => {
|
|
send_one_line!(
|
|
self,
|
|
"HISTORY SET MESSAGE_TYPE_ORDERING \"{}\"",
|
|
ordering
|
|
.iter()
|
|
.map(|x| x.to_string())
|
|
.collect::<Vec<String>>()
|
|
.join(" ")
|
|
)
|
|
}
|
|
Request::HistorySearch(scope, condition) => {
|
|
send_one_line!(self, "HISTORY SEARCH {} \"{}\"", scope, condition)
|
|
}
|
|
Request::Quit => send_one_line!(self, "QUIT"),
|
|
}?;
|
|
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))
|
|
}
|
|
|
|
/// Initiate communitation to send text to speak
|
|
pub fn speak(&mut self) -> ClientResult<&mut Self> {
|
|
self.send(Request::Speak)
|
|
}
|
|
|
|
/// Speak a char
|
|
pub fn speak_char(&mut self, ch: char) -> ClientResult<&mut Self> {
|
|
self.send(Request::SpeakChar(ch))
|
|
}
|
|
|
|
/// 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(Request::Stop(scope))
|
|
}
|
|
|
|
/// Cancel current message
|
|
pub fn cancel(&mut self, scope: MessageScope) -> ClientResult<&mut Self> {
|
|
self.send(Request::Cancel(scope))
|
|
}
|
|
|
|
/// Pause current message
|
|
pub fn pause(&mut self, scope: MessageScope) -> ClientResult<&mut Self> {
|
|
self.send(Request::Pause(scope))
|
|
}
|
|
|
|
/// Resume current message
|
|
pub fn resume(&mut self, scope: MessageScope) -> ClientResult<&mut Self> {
|
|
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: u32) -> ClientResult<&mut Self> {
|
|
self.send(Request::SetPauseContext(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)
|
|
}
|
|
|
|
/// 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))
|
|
}
|
|
|
|
/// Get clients in history.
|
|
pub fn history_get_clients(&mut self) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistoryGetClients)
|
|
}
|
|
|
|
/// Get client id in the history.
|
|
pub fn history_get_client_id(&mut self) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistoryGetClientId)
|
|
}
|
|
|
|
/// Get last message said.
|
|
pub fn history_get_last(&mut self) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistoryGetLastMsgId)
|
|
}
|
|
|
|
/// Get a range of client messages.
|
|
pub fn history_get_client_messages(
|
|
&mut self,
|
|
scope: ClientScope,
|
|
start: u32,
|
|
number: u32,
|
|
) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistoryGetClientMsgs(scope, start, number))
|
|
}
|
|
|
|
/// Get the id of the last message sent by the client.
|
|
pub fn history_get_last_message_id(&mut self) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistoryGetLastMsgId)
|
|
}
|
|
|
|
/// Return the text of an history message.
|
|
pub fn history_get_message(&mut self, msg_id: MessageId) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistoryGetMsg(msg_id))
|
|
}
|
|
|
|
/// Get the id of the message the history cursor is pointing to.
|
|
pub fn history_get_cursor(&mut self) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistoryCursorGet)
|
|
}
|
|
|
|
/// Set the history cursor position.
|
|
pub fn history_set_cursor(
|
|
&mut self,
|
|
scope: ClientScope,
|
|
pos: HistoryPosition,
|
|
) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistoryCursorSet(scope, pos))
|
|
}
|
|
|
|
/// Move the cursor position backward or forward.
|
|
pub fn history_move_cursor(&mut self, direction: CursorDirection) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistoryCursorMove(direction))
|
|
}
|
|
|
|
/// Speak the message from history.
|
|
pub fn history_speak(&mut self, msg_id: MessageId) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistorySpeak(msg_id))
|
|
}
|
|
|
|
/// Sort messages in history.
|
|
pub fn history_sort(
|
|
&mut self,
|
|
direction: SortDirection,
|
|
key: SortKey,
|
|
) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistorySort(direction, key))
|
|
}
|
|
|
|
/// Set the maximum length of short versions of history messages.
|
|
pub fn history_set_short_message_length(&mut self, length: u32) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistorySetShortMsgLength(length))
|
|
}
|
|
|
|
/// Set the ordering of the message types, from the minimum to the maximum.
|
|
pub fn history_set_ordering(&mut self, ordering: Vec<Ordering>) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistorySetMsgTypeOrdering(ordering))
|
|
}
|
|
|
|
/// Search in message history.
|
|
pub fn history_search(
|
|
&mut self,
|
|
scope: ClientScope,
|
|
condition: &str,
|
|
) -> ClientResult<&mut Self> {
|
|
self.send(Request::HistorySearch(scope, condition.to_string()))
|
|
}
|
|
|
|
/// Close the connection
|
|
pub fn quit(&mut self) -> ClientResult<&mut Self> {
|
|
self.send(Request::Quit)
|
|
}
|
|
|
|
/// Receive answer from server
|
|
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_CLIENTS_LIST_SENT => Ok(Response::HistoryClientListSent(parse_typed_lines::<
|
|
HistoryClientStatus,
|
|
>(&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_integer(&lines)?)),
|
|
OK_MSG_TEXT_SENT => Ok(Response::MessageTextSent),
|
|
OK_HELP_SENT => Ok(Response::HelpSent(lines)),
|
|
OK_VOICES_LIST_SENT => Ok(Response::VoicesListSent(
|
|
parse_typed_lines::<SynthesisVoice>(&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| {
|
|
if status.code == expected_code {
|
|
Ok(self)
|
|
} else {
|
|
Err(ClientError::UnexpectedStatus(status.code))
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Receive lines
|
|
pub fn receive_lines(&mut self, expected_code: ReturnCode) -> ClientResult<Vec<String>> {
|
|
let mut lines = Vec::new();
|
|
let status = self.receive_answer(&mut lines)?;
|
|
if status.code == expected_code {
|
|
Ok(lines)
|
|
} else {
|
|
Err(ClientError::UnexpectedStatus(status.code))
|
|
}
|
|
}
|
|
|
|
/// Receive a single string
|
|
pub fn receive_string(&mut self, expected_code: ReturnCode) -> ClientResult<String> {
|
|
self.receive_lines(expected_code)
|
|
.and_then(|lines| parse_single_value(&lines))
|
|
}
|
|
|
|
/// Receive signed 8-bit integer
|
|
pub fn receive_i8(&mut self) -> ClientResult<u8> {
|
|
self.receive_string(OK_GET).and_then(|s| {
|
|
s.parse()
|
|
.map_err(|_| ClientError::invalid_data("invalid signed integer"))
|
|
})
|
|
}
|
|
|
|
/// Receive unsigned 8-bit integer
|
|
pub fn receive_u8(&mut self) -> ClientResult<u8> {
|
|
self.receive_string(OK_GET).and_then(|s| {
|
|
s.parse()
|
|
.map_err(|_| ClientError::invalid_data("invalid unsigned 8-bit integer"))
|
|
})
|
|
}
|
|
|
|
/// Receive cursor pos
|
|
pub fn receive_cursor_pos(&mut self) -> ClientResult<u16> {
|
|
self.receive_string(OK_CUR_POS_RET).and_then(|s| {
|
|
s.parse()
|
|
.map_err(|_| ClientError::invalid_data("invalid unsigned 16-bit integer"))
|
|
})
|
|
}
|
|
|
|
/// Receive message id
|
|
pub fn receive_message_id(&mut self) -> ClientResult<MessageId> {
|
|
let mut lines = Vec::new();
|
|
match self.receive_answer(&mut lines)?.code {
|
|
OK_MESSAGE_QUEUED | OK_LAST_MSG => Ok(parse_single_integer(&lines)?),
|
|
_ => Err(ClientError::invalid_data("not a message id")),
|
|
}
|
|
}
|
|
|
|
/// Receive client id
|
|
pub fn receive_client_id(&mut self) -> ClientResult<ClientId> {
|
|
self.receive_string(OK_CLIENT_ID_SENT).and_then(|s| {
|
|
s.parse()
|
|
.map_err(|_| ClientError::invalid_data("invalid client id"))
|
|
})
|
|
}
|
|
|
|
/// 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| parse_typed_lines::<SynthesisVoice>(&lines))
|
|
}
|
|
|
|
/// Receive a notification
|
|
pub fn receive_event(&mut self) -> ClientResult<Event> {
|
|
let mut lines = Vec::new();
|
|
crate::protocol::receive_answer(&mut self.input, Some(&mut lines)).and_then(|status| {
|
|
if lines.len() < 2 {
|
|
Err(ClientError::unexpected_eof("event truncated"))
|
|
} else {
|
|
let message = &lines[0];
|
|
let client = &lines[1];
|
|
match status.code {
|
|
700 => {
|
|
if lines.len() != 3 {
|
|
Err(ClientError::unexpected_eof("index markevent truncated"))
|
|
} else {
|
|
let mark = lines[3].to_owned();
|
|
Ok(Event::index_mark(mark, message, client))
|
|
}
|
|
}
|
|
701 => Ok(Event::begin(message, client)),
|
|
702 => Ok(Event::end(message, client)),
|
|
703 => Ok(Event::cancel(message, client)),
|
|
704 => Ok(Event::pause(message, client)),
|
|
705 => Ok(Event::resume(message, client)),
|
|
_ => Err(ClientError::invalid_data("wrong status code for event")),
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Receive a list of client status from history.
|
|
pub fn receive_history_clients(&mut self) -> ClientResult<Vec<HistoryClientStatus>> {
|
|
self.receive_lines(OK_CLIENTS_LIST_SENT)
|
|
.and_then(|lines| parse_typed_lines::<HistoryClientStatus>(&lines))
|
|
}
|
|
|
|
/// Check the result of `set_client_name`.
|
|
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 Self> {
|
|
self.check_status(OK_RECEIVING_DATA)
|
|
}
|
|
|
|
/// Register the socket for polling.
|
|
#[cfg(feature = "async-mio")]
|
|
pub fn register(
|
|
&mut self,
|
|
poll: &mio::Poll,
|
|
input_token: mio::Token,
|
|
output_token: mio::Token,
|
|
) -> io::Result<()> {
|
|
poll.registry()
|
|
.register(self.input.get_mut(), input_token, mio::Interest::READABLE)?;
|
|
poll.registry()
|
|
.register(self.output.get_mut(), output_token, mio::Interest::WRITABLE)?;
|
|
Ok(())
|
|
}
|
|
}
|