Initial work on a tokio/async-std feature

main
Tait Hoyem 2 years ago
parent 12b080800a
commit 0187c049cf

@ -16,12 +16,17 @@ mio = { version = "0.8", optional = true }
thiserror = "1"
strum = "0.24"
strum_macros = "0.24"
tokio = { version = "^1.21.2", features = ["io-util"] }
[features]
async-mio = ["mio/net", "mio/os-poll"]
tokio = ["tokio/io-util"]
async-std = ["async-std/default"]
[dev-dependencies]
mio = { version = "0.8", features = ["os-poll", "os-ext"] }
tokio = { version = "^1.21.2", features = ["io-util"] }
async-std = { version = "1.12.0" }
lazy_static = "1"
popol = "1"
tempfile = "3"

@ -0,0 +1,287 @@
// 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,
flush_lines_async_std, write_lines_async_std,
};
use crate::types::*;
use async_std::io::{AsyncBufRead, AsyncWrite};
/// 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 async 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 AsyncClient<R: AsyncBufRead + Unpin, W: AsyncWrite + Unpin> {
input: R,
output: W,
}
impl<R: AsyncBufRead + Unpin, W: AsyncWrite + Unpin> AsyncClient<R, W> {
pub(crate) fn new(input: R, output: W) -> Self {
Self { input, output }
}
/// Send lines of text (terminated by a single dot).
pub async fn send_lines(&mut self, lines: &[String]) -> ClientResult<&mut Self> {
const END_OF_DATA: [&str; 1] = ["."];
write_lines_tokio(
&mut self.output,
lines
.iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>()
.as_slice(),
).await?;
flush_lines_tokio(&mut self.output, &END_OF_DATA).await?;
Ok(self)
}
/// Receive answer from server
async fn receive_answer(&mut self, lines: &mut Vec<String>) -> ClientStatus {
crate::protocol::receive_answer_tokio(&mut self.input, Some(lines)).await
}
/// Receive one response.
pub async 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).await?;
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"),
}
}
}

@ -12,7 +12,7 @@ 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,
write_lines
};
use crate::types::*;

@ -42,6 +42,11 @@ pub mod tcp;
#[cfg(any(not(feature = "async-mio"), doc))]
pub use client::Client;
#[cfg(any(feature = "tokio", doc))]
pub mod tokio;
#[cfg(any(feature = "async-std", doc))]
pub mod async_std;
pub use client::{Request, Response};
pub use constants::*;
pub use poll::QueuedClient;

@ -9,6 +9,16 @@
use log::debug;
use std::io::{self, BufRead, Write};
#[cfg(feature = "tokio")]
use tokio::io::{
AsyncWrite, AsyncWriteExt,
AsyncBufRead, AsyncBufReadExt,
};
#[cfg(feature = "async-std")]
use async_std::io::{
AsyncWrite, AsyncRead,
};
use std::str::FromStr;
use crate::types::{ClientError, ClientResult, ClientStatus, EventId, StatusLine};
@ -73,12 +83,40 @@ pub(crate) fn write_lines(output: &mut dyn Write, lines: &[&str]) -> ClientResul
Ok(())
}
/// Write lines (asyncronously) separated by CRLF.
#[cfg(feature = "tokio")]
pub(crate) async fn write_lines_tokio(output: &mut (dyn AsyncWrite + Unpin), lines: &[&str]) -> ClientResult<()> {
for line in lines.iter() {
debug!("SSIP(out): {}", line);
output.write_all(line.as_bytes()).await?;
output.write_all(b"\r\n").await?;
}
Ok(())
}
/// Write lines (asyncronously) separated by CRLF.
#[cfg(feature = "async-std")]
pub(crate) async fn write_lines_tokio(output: &mut (dyn AsyncWrite + Unpin), lines: &[&str]) -> ClientResult<()> {
for line in lines.iter() {
debug!("SSIP(out): {}", line);
output.write_all(line.as_bytes()).await?;
output.write_all(b"\r\n").await?;
}
Ok(())
}
/// Write lines separated by CRLF and flush the output.
pub(crate) fn flush_lines(output: &mut dyn Write, lines: &[&str]) -> ClientResult<()> {
write_lines(output, lines)?;
output.flush()?;
Ok(())
}
/// Write lines separated by CRLF and flush the output asyncronously.
#[cfg(feature = "tokio")]
pub(crate) async fn flush_lines_tokio(output: &mut (dyn AsyncWrite + Unpin), lines: &[&str]) -> ClientResult<()> {
write_lines_tokio(output, lines).await?;
output.flush().await?;
Ok(())
}
/// Strip prefix if found
fn strip_prefix(line: &str, prefix: &str) -> String {
@ -99,6 +137,36 @@ fn parse_status_line(code: u16, line: &str) -> ClientStatus {
}
/// Read lines from server until a status line is found.
#[cfg(feature = "tokio")]
pub(crate) async fn receive_answer_tokio(
input: &mut (dyn AsyncBufRead + Unpin),
mut lines: Option<&mut Vec<String>>,
) -> ClientStatus {
loop {
let mut line = String::new();
input.read_line(&mut line).await.map_err(ClientError::Io)?;
debug!("SSIP(in): {}", line.trim_end());
match line.chars().nth(3) {
Some(ch) => match ch {
' ' => match line[0..3].parse::<u16>() {
Ok(code) => return parse_status_line(code, line[4..].trim_end()),
Err(err) => return Err(invalid_input!(err.to_string())),
},
'-' => match lines {
Some(ref mut lines) => lines.push(line[4..].trim_end().to_string()),
None => return Err(invalid_input!("unexpected line: {}", line)),
},
ch => {
return Err(invalid_input!("expecting space or dash, got {}.", ch));
}
},
None if line.is_empty() => return Err(invalid_input!("empty line")),
None => return Err(invalid_input!("line too short: {}", line)),
}
}
}
/// Read lines from server until a status line is found asyncronously.
pub(crate) fn receive_answer(
input: &mut dyn BufRead,
mut lines: Option<&mut Vec<String>>,

@ -0,0 +1,289 @@
#[cfg(all(feature = "tokio"))]
// 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,
flush_lines_tokio, write_lines_tokio,
};
use crate::types::*;
use tokio::io::{AsyncBufRead, AsyncWrite};
/// 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 async 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 AsyncClient<R: AsyncBufRead + Unpin, W: AsyncWrite + Unpin> {
input: R,
output: W,
}
impl<R: AsyncBufRead + Unpin, W: AsyncWrite + Unpin> AsyncClient<R, W> {
pub(crate) fn new(input: R, output: W) -> Self {
Self { input, output }
}
/// Send lines of text (terminated by a single dot).
pub async fn send_lines(&mut self, lines: &[String]) -> ClientResult<&mut Self> {
const END_OF_DATA: [&str; 1] = ["."];
write_lines_tokio(
&mut self.output,
lines
.iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>()
.as_slice(),
).await?;
flush_lines_tokio(&mut self.output, &END_OF_DATA).await?;
Ok(self)
}
/// Receive answer from server
async fn receive_answer(&mut self, lines: &mut Vec<String>) -> ClientStatus {
crate::protocol::receive_answer_tokio(&mut self.input, Some(lines)).await
}
/// Receive one response.
pub async 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).await?;
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"),
}
}
}
Loading…
Cancel
Save