diff --git a/examples/hello.rs b/examples/hello.rs index 43e718d..5c3d3e1 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,7 +1,7 @@ use ssip_client::{ClientName, ClientResult}; fn main() -> ClientResult<()> { - let mut client = ssip_client::new_default_fifo_client(&ClientName::new("joe", "list"), None)?; + let mut client = ssip_client::new_default_fifo_client(&ClientName::new("joe", "hello"), None)?; let msg_id = client.say_line("hello")?; println!("message: {}", msg_id); client.quit()?; diff --git a/examples/notifications.rs b/examples/notifications.rs new file mode 100644 index 0000000..828d987 --- /dev/null +++ b/examples/notifications.rs @@ -0,0 +1,28 @@ +use ssip_client::{ClientName, ClientResult, EventType, NotificationType}; + +fn main() -> ClientResult<()> { + let mut client = + ssip_client::new_default_fifo_client(&ClientName::new("joe", "notifications"), None)?; + client.enable_notification(NotificationType::All).unwrap(); + let msg_id = client.say_line("hello")?; + println!("message: {}", msg_id); + loop { + match client.receive_event() { + Ok(event) => { + println!( + "event {}: message {} client {}", + event.ntype, event.message, event.client + ); + if matches!(event.ntype, EventType::End) { + break; + } + } + Err(err) => { + eprintln!("error: {:?}", err); + break; + } + } + } + client.quit()?; + Ok(()) +} diff --git a/src/client.rs b/src/client.rs index 1bfa755..8bc3f29 100644 --- a/src/client.rs +++ b/src/client.rs @@ -13,23 +13,25 @@ use thiserror::Error as ThisError; use crate::constants::OK_RECEIVING_DATA; use crate::types::{ - CapitalLettersRecognitionMode, ClientScope, KeyName, MessageId, MessageScope, Priority, - PunctuationMode, StatusLine, SynthesisVoice, + CapitalLettersRecognitionMode, ClientScope, Event, KeyName, MessageId, MessageScope, + NotificationType, Priority, PunctuationMode, StatusLine, SynthesisVoice, }; /// 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("SSIP: {0}")] - Ssip(StatusLine), #[error("No line in result")] NoLine, + #[error("SSIP: {0}")] + Ssip(StatusLine), #[error("Too many lines")] TooManyLines, - #[error("Invalid type")] - InvalidType, + #[error("Truncated message")] + TruncatedMessage, } impl From for ClientError { @@ -445,6 +447,49 @@ impl Client { client_setter!(block_end, "End a block", "BLOCK END"); client_setter!(quit, "Close the connection", "QUIT"); + + client_setter!( + enable_notification, + "Enable notification events", + value as NotificationType, + "SET self NOTIFICATION {} on" + ); + + client_setter!( + disable_notification, + "Disable notification events", + value as NotificationType, + "SET self NOTIFICATION {} off" + ); + + /// Receive a notification + pub fn receive_event(&mut self) -> ClientResult { + let mut lines = Vec::new(); + crate::protocol::receive_answer(&mut self.input, Some(&mut lines)).and_then(|status| { + if lines.len() < 2 { + Err(ClientError::TruncatedMessage) + } else { + let message = lines[0].to_owned(); + let client = lines[1].to_owned(); + match status.code { + 700 => { + if lines.len() != 3 { + Err(ClientError::TruncatedMessage) + } 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::InvalidType), + } + } + }) + } } #[cfg(test)] diff --git a/src/types.rs b/src/types.rs index c656f6f..85b1b3f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -229,6 +229,78 @@ pub enum KeyName { Window, } +/// Notification type +#[derive(StrumDisplay, Debug)] +pub enum NotificationType { + #[strum(serialize = "begin")] + Begin, + #[strum(serialize = "end")] + End, + #[strum(serialize = "cancel")] + Cancel, + #[strum(serialize = "pause")] + Pause, + #[strum(serialize = "resume")] + Resume, + #[strum(serialize = "index_mark")] + IndexMark, + #[strum(serialize = "all")] + All, +} + +/// Notification event type (returned by server) +#[derive(StrumDisplay, Debug)] +pub enum EventType { + Begin, + End, + Cancel, + Pause, + Resume, + IndexMark(String), +} + +/// Notification event +#[derive(Debug)] +pub struct Event { + pub ntype: EventType, + pub message: String, + pub client: String, +} + +impl Event { + pub fn new(ntype: EventType, message: String, client: String) -> Event { + Event { + ntype, + message, + client, + } + } + + pub fn begin(message: String, client: String) -> Event { + Event::new(EventType::Begin, message, client) + } + + pub fn end(message: String, client: String) -> Event { + Event::new(EventType::End, message, client) + } + + pub fn index_mark(mark: String, message: String, client: String) -> Event { + Event::new(EventType::IndexMark(mark), message, client) + } + + pub fn cancel(message: String, client: String) -> Event { + Event::new(EventType::Cancel, message, client) + } + + pub fn pause(message: String, client: String) -> Event { + Event::new(EventType::Pause, message, client) + } + + pub fn resume(message: String, client: String) -> Event { + Event::new(EventType::Resume, message, client) + } +} + /// Synthesis voice #[derive(Debug, PartialEq)] pub struct SynthesisVoice { diff --git a/tests/fifo_client_tests.rs b/tests/fifo_client_tests.rs index faa95ed..01d631b 100644 --- a/tests/fifo_client_tests.rs +++ b/tests/fifo_client_tests.rs @@ -41,7 +41,7 @@ impl Server { for question in questions.iter() { let mut line = String::new(); input.read_line(&mut line)?; - if line != dbg!(*question) { + if line != *question { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("read <{}> instead of <{}>", dbg!(line), *question), @@ -76,6 +76,8 @@ impl Server { } /// Create a server and run the client +/// +/// The communication is an array of (["question", ...], "response") fn test_client( communication: &'static [(&'static [&'static str], &'static str)], process: F, @@ -380,3 +382,29 @@ fn list_synthesis_voices() -> io::Result<()> { }, ) } + +#[test] +fn receive_notification() -> io::Result<()> { + test_client( + &[ + SET_CLIENT_COMMUNICATION, + (&["SPEAK\r\n"], "230 OK RECEIVING DATA\r\n"), + ( + &["Hello, world\r\n", ".\r\n"], + "225-21\r\n225 OK MESSAGE QUEUED\r\n701-21\r\n701-test\r\n701 BEGIN\r\n", + ), + ], + |client| { + assert_eq!("21", client.say_line("Hello, world").unwrap(),); + match client.receive_event() { + Ok(Event { + ntype: EventType::Begin, + message: _, + client: _, + }) => Ok(()), + Ok(_) => panic!("wrong event"), + Err(_) => panic!("error on event"), + } + }, + ) +}