Implement history get client list.

main
Laurent Pelecq 2 years ago
parent 1a2e7fc145
commit 9e4b09d96d

@ -11,7 +11,7 @@ use std::io::{self, Read, Write};
use crate::constants::*;
use crate::protocol::{
flush_lines, parse_event_id, parse_single_value, parse_synthesis_voices, write_lines,
flush_lines, parse_event_id, parse_single_value, parse_typed_lines, write_lines,
};
use crate::types::*;
@ -96,57 +96,57 @@ pub enum Request {
#[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
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(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 {
@ -297,7 +297,7 @@ impl<S: Read + Write + Source> Client<S> {
}
Request::Begin => send_one_line!(self, "BLOCK BEGIN"),
Request::End => send_one_line!(self, "BLOCK END"),
Request::HistoryGetClients => panic!("not implemented"),
Request::HistoryGetClients => send_one_line!(self, "HISTORY GET CLIENT_LIST"),
Request::HistoryGetClientId => panic!("not implemented"),
Request::HistoryGetClientMsgs(_scope, _start, _number) => panic!("not implemented"),
Request::HistoryGetLastMsgId => panic!("not implemented"),
@ -646,7 +646,9 @@ impl<S: Read + Write + Source> Client<S> {
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_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)?)),
@ -654,7 +656,9 @@ impl<S: Read + Write + Source> Client<S> {
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_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),
@ -726,7 +730,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| parse_synthesis_voices(&lines))
.and_then(|lines| parse_typed_lines::<SynthesisVoice>(&lines))
}
/// Receive a notification
@ -758,6 +762,12 @@ impl<S: Read + Write + Source> Client<S> {
})
}
/// 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)

@ -97,7 +97,7 @@ pub const OK_RECEIVING_DATA: ReturnCode = 230;
pub const OK_BYE: ReturnCode = 231;
/// Successful completion: OK CLIENTS LIST SENT
pub const OK_CLIENT_LIST_SENT: ReturnCode = 240;
pub const OK_CLIENTS_LIST_SENT: ReturnCode = 240;
/// Successful completion: OK MSGS LIST SENT
pub const OK_MSGS_LIST_SENT: ReturnCode = 241;

@ -11,7 +11,7 @@ use log::debug;
use std::io::{self, BufRead, Write};
use std::str::FromStr;
use crate::types::{ClientError, ClientResult, ClientStatus, EventId, StatusLine, SynthesisVoice};
use crate::types::{ClientError, ClientResult, ClientStatus, EventId, StatusLine};
macro_rules! invalid_input {
($msg:expr) => {
@ -40,13 +40,14 @@ pub(crate) fn parse_event_id(lines: &[String]) -> ClientResult<EventId> {
}
}
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)
pub(crate) fn parse_typed_lines<T>(lines: &[String]) -> ClientResult<Vec<T>>
where
T: FromStr<Err = io::Error>,
{
lines
.iter()
.map(|line| T::from_str(line.as_str()).map_err(|err| ClientError::from(err)))
.collect::<ClientResult<Vec<T>>>()
}
/// Write lines separated by CRLF.
@ -120,6 +121,8 @@ mod tests {
use super::{receive_answer, ClientError, ClientResult};
use crate::types::SynthesisVoice;
#[test]
fn single_ok_status_line() {
let mut input = BufReader::new("208 OK CLIENT NAME SET\r\n".as_bytes());
@ -225,7 +228,7 @@ mod tests {
.iter()
.map(|s| s.to_string())
.collect::<Vec<String>>();
let voices = super::parse_synthesis_voices(&lines)?;
let voices = super::parse_typed_lines::<SynthesisVoice>(&lines)?;
assert_eq!(3, voices.len());
assert_eq!("en", voices[0].name.as_str());
assert_eq!(Some(String::from("af")), voices[1].language);

@ -21,7 +21,7 @@ pub type ReturnCode = u16;
pub type MessageId = u32;
/// Client identifier
pub type ClientId = String;
pub type ClientId = u32;
/// Message identifiers
#[derive(Debug, Clone)]
@ -522,12 +522,72 @@ impl fmt::Display for HistoryPosition {
}
}
/// History client status
#[derive(Debug, PartialEq)]
pub struct HistoryClientStatus {
pub id: ClientId,
pub name: String,
pub connected: bool,
}
impl HistoryClientStatus {
pub fn new(id: ClientId, name: &str, connected: bool) -> Self {
Self {
id,
name: name.to_string(),
connected,
}
}
fn invalid_data(msg: &str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, msg)
}
fn unexpected_eof(msg: &str) -> io::Error {
io::Error::new(io::ErrorKind::UnexpectedEof, msg)
}
}
impl FromStr for HistoryClientStatus {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut iter = s.splitn(3, ' ');
match iter.next() {
Some("") => Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"expecting client id",
)),
Some(client_id) => match client_id.parse::<u32>() {
Ok(id) => match iter.next() {
Some(name) => match iter.next() {
Some(status) if status == "0" => {
Ok(HistoryClientStatus::new(id, name, false))
}
Some(status) if status == "1" => {
Ok(HistoryClientStatus::new(id, name, true))
}
Some(_) => Err(HistoryClientStatus::invalid_data("invalid client status")),
None => Err(HistoryClientStatus::unexpected_eof(
"expecting client status",
)),
},
None => Err(HistoryClientStatus::unexpected_eof("expecting client name")),
},
Err(_) => Err(HistoryClientStatus::invalid_data("invalid client id")),
},
None => Err(HistoryClientStatus::unexpected_eof("expecting client id")),
}
}
}
#[cfg(test)]
mod tests {
use std::io;
use std::str::FromStr;
use super::{MessageScope, SynthesisVoice};
use super::{HistoryClientStatus, HistoryPosition, MessageScope, SynthesisVoice};
#[test]
fn parse_synthesis_voice() {
@ -551,4 +611,48 @@ mod tests {
assert_eq!("all", format!("{}", MessageScope::All).as_str());
assert_eq!("123", format!("{}", MessageScope::Message(123)).as_str());
}
#[test]
fn format_history_position() {
assert_eq!("first", format!("{}", HistoryPosition::First).as_str());
assert_eq!("last", format!("{}", HistoryPosition::Last).as_str());
assert_eq!("pos 15", format!("{}", HistoryPosition::Pos(15)).as_str());
}
#[test]
fn parse_history_client_status() {
assert_eq!(
HistoryClientStatus::new(10, "joe:speechd_client:main", false),
HistoryClientStatus::from_str("10 joe:speechd_client:main 0").unwrap()
);
assert_eq!(
HistoryClientStatus::new(11, "joe:speechd_client:main", true),
HistoryClientStatus::from_str("11 joe:speechd_client:main 1").unwrap()
);
for line in &[
"9 joe:speechd_client:main xxx",
"xxx joe:speechd_client:main 1",
] {
match HistoryClientStatus::from_str(line) {
Ok(_) => panic!("parsing should have failed"),
Err(err) => assert_eq!(
io::ErrorKind::InvalidData,
err.kind(),
"expecting error 'invalid data' parsing \"{}\"",
line
),
}
}
for line in &["8 joe:speechd_client:main", "8", ""] {
match HistoryClientStatus::from_str(line) {
Ok(_) => panic!("parsing should have failed"),
Err(err) => assert_eq!(
io::ErrorKind::UnexpectedEof,
err.kind(),
"expecting error 'unexpected EOF' parsing \"{}\"",
line
),
}
}
}
}

@ -396,3 +396,29 @@ fn receive_notification() -> ClientResult<()> {
},
)
}
#[test]
#[cfg(not(feature = "async-mio"))]
fn history_clients_list() -> ClientResult<()> {
test_client(
&[
SET_CLIENT_COMMUNICATION,
(
"HISTORY GET CLIENT_LIST\r\n",
"240-0 joe:speechd_client:main 0\r\n240-1 joe:speechd_client:status 0\r\n240-2 unknown:unknown:unknown 1\r\n240 OK CLIENTS LIST SENT\r\n"
),
],
|client| {
let statuses = client.history_get_clients().unwrap().receive_history_clients().unwrap();
let expected_statuses: [HistoryClientStatus; 3] = [ HistoryClientStatus::new(0, "joe:speechd_client:main", false),
HistoryClientStatus::new(1, "joe:speechd_client:status", false),
HistoryClientStatus::new(2, "unknown:unknown:unknown", true),
];
assert_eq!(expected_statuses.len(), statuses.len());
for (expected, found) in expected_statuses.iter().zip(statuses.iter()) {
assert_eq!(*expected, *found);
}
Ok(())
},
)
}

Loading…
Cancel
Save