diff --git a/rups/examples/blocking.rs b/rups/examples/blocking.rs index 8b1ff67..fde44e3 100644 --- a/rups/examples/blocking.rs +++ b/rups/examples/blocking.rs @@ -1,7 +1,7 @@ use std::convert::TryInto; use std::env; -use rups::blocking::Connection; +use rups::blocking::Client; use rups::{Auth, ConfigBuilder}; fn main() -> rups::Result<()> { @@ -22,7 +22,7 @@ fn main() -> rups::Result<()> { .with_debug(false) // Turn this on for debugging network chatter .build(); - let mut conn = Connection::new(&config)?; + let mut conn = Client::new(&config)?; // Get server information println!("NUT server:"); @@ -34,34 +34,34 @@ fn main() -> rups::Result<()> { for (name, description) in conn.list_ups()? { println!("\t- Name: {}", name); println!("\t Description: {}", description); - println!("\t Number of logins: {}", conn.get_num_logins(&name)?); - - // Get list of mutable variables - let mutable_vars = conn.list_mutable_vars(&name)?; - - // List UPS variables (key = val) - println!("\t Mutable Variables:"); - for var in mutable_vars.iter() { - println!("\t\t- {}", var); - println!("\t\t {:?}", conn.get_var_type(&name, var.name())?); - } - - // List UPS immutable properties (key = val) - println!("\t Immutable Properties:"); - for var in conn.list_vars(&name)? { - if mutable_vars.iter().any(|v| v.name() == var.name()) { - continue; - } - println!("\t\t- {}", var); - println!("\t\t {:?}", conn.get_var_type(&name, var.name())?); - } - - // List UPS commands - println!("\t Commands:"); - for cmd in conn.list_commands(&name)? { - let description = conn.get_command_description(&name, &cmd)?; - println!("\t\t- {} ({})", cmd, description); - } + // println!("\t Number of logins: {}", conn.get_num_logins(&name)?); + // + // // Get list of mutable variables + // let mutable_vars = conn.list_mutable_vars(&name)?; + // + // // List UPS variables (key = val) + // println!("\t Mutable Variables:"); + // for var in mutable_vars.iter() { + // println!("\t\t- {}", var); + // println!("\t\t {:?}", conn.get_var_type(&name, var.name())?); + // } + // + // // List UPS immutable properties (key = val) + // println!("\t Immutable Properties:"); + // for var in conn.list_vars(&name)? { + // if mutable_vars.iter().any(|v| v.name() == var.name()) { + // continue; + // } + // println!("\t\t- {}", var); + // println!("\t\t {:?}", conn.get_var_type(&name, var.name())?); + // } + // + // // List UPS commands + // println!("\t Commands:"); + // for cmd in conn.list_commands(&name)? { + // let description = conn.get_command_description(&name, &cmd)?; + // println!("\t\t- {} ({})", cmd, description); + // } } // Gracefully shut down the connection using the `LOGOUT` command diff --git a/rups/src/blocking/client.rs b/rups/src/blocking/client.rs index 4d8fae7..3ebed52 100644 --- a/rups/src/blocking/client.rs +++ b/rups/src/blocking/client.rs @@ -107,4 +107,10 @@ impl Client { fn enable_ssl(self) -> crate::Result { Ok(self) } + + /// Gracefully closes the connection. + pub fn close(mut self) -> crate::Result<()> { + self.exec_logout()?; + Ok(()) + } } diff --git a/rups/src/cmd.rs b/rups/src/cmd.rs index ad16273..0ad1d34 100644 --- a/rups/src/cmd.rs +++ b/rups/src/cmd.rs @@ -743,6 +743,132 @@ macro_rules! implement_client_action_commands { }; } +/// A macro for implementing client action commands that return a string. +/// +/// Each function should return the sentence to pass. +macro_rules! implement_client_simple_commands { + ( + $( + $(#[$attr:meta])+ + $vis:vis fn $name:ident($($argname:ident: $argty:ty),*) -> String { + $cmd:block + } + )* + ) => { + impl crate::blocking::Client { + $( + $(#[$attr])* + #[allow(dead_code)] + $vis fn $name(&mut self$(, $argname: $argty)*) -> crate::Result { + use crate::proto::{Sentence, ServerSentences::*}; + self.stream + .write_sentence(&$cmd)?; + self.stream + .read_literal() + .map(|s| String::from(s.trim_end_matches('\n'))) + } + )* + } + + #[cfg(feature = "async")] + impl crate::tokio::Client { + $( + $(#[$attr])* + #[allow(dead_code)] + $vis async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result { + use crate::proto::{Sentence, ServerSentences::*}; + self.stream + .write_sentence(&$cmd) + .await?; + self.stream + .read_literal() + .await + .map(|s| String::from(s.trim_end_matches('\n'))) + } + )* + } + }; +} + +/// A macro for implementing client action commands that return a list of objects. +/// +/// Each function should return: +/// 1. The sentence to execute the query +/// 2. The expected 'BEGIN' sentence +/// 3. The expected return sentence +/// 4. The mapper for individual items +/// 5. The expected 'END' sentence +macro_rules! implement_client_list_commands { + ( + $( + $(#[$attr:meta])+ + $vis:vis fn $name:ident($($argname:ident: $argty:ty),*) -> $ret:ty { + $cmd:expr, + $begin:pat_param, + $itemsentence:pat_param, + $mapper:block, + $end:pat_param + } + )* + ) => { + impl crate::blocking::Client { + $( + $(#[$attr])* + #[allow(dead_code)] + $vis fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$ret> { + use crate::proto::{Sentence, ClientSentences, ClientSentences::*, ServerSentences::*}; + self.stream + .write_sentence(&$cmd)?; + self.stream + .read_sentence::()? + .matching(|s| matches!(s, $begin))?; + let sentences: Vec = self.stream + .read_sentences_until(|s| matches!(s, $end))?; + sentences + .into_iter() + .map(|s| { + match s { + $itemsentence => Ok($mapper), + _ => Err(crate::Error::Nut(crate::NutError::UnexpectedResponse)), + } + }) + .collect() + } + )* + } + + #[cfg(feature = "async")] + impl crate::tokio::Client { + $( + $(#[$attr])* + #[allow(dead_code)] + $vis async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$ret> { + use crate::proto::{Sentence, ClientSentences, ClientSentences::*, ServerSentences::*}; + self.stream + .write_sentence(&$cmd) + .await?; + self.stream + .read_sentence::() + .await? + .matching(|s| matches!(s, $begin))?; + let sentences: Vec = self.stream + .read_sentences_until(|s| matches!(s, $end)) + .await?; + sentences + .into_iter() + .map(|s| { + match s { + $itemsentence => Ok($mapper), + _ => Err(crate::Error::Nut(crate::NutError::UnexpectedResponse)), + } + }) + .collect() + } + )* + } + }; +} + implement_list_commands! { /// Queries a list of UPS devices. pub fn list_ups() -> Vec<(String, String)> { @@ -917,3 +1043,26 @@ implement_client_action_commands! { { StartTLSOk {} } } } + +implement_client_simple_commands! { + /// Queries the network protocol version. + pub fn get_network_version() -> String { + { QueryNetworkVersion {} } + } + + /// Queries the server NUT version. + pub fn get_server_version() -> String { + { QueryVersion {} } + } +} + +implement_client_list_commands! { + /// Queries the network protocol version. + pub fn list_ups() -> Vec<(String, String)> { + QueryListUps {}, + BeginListUps {}, + RespondUps {ups_name, description}, + { (ups_name, description) }, + EndListUps {} + } +} diff --git a/rups/src/proto/mod.rs b/rups/src/proto/mod.rs index 20702ad..00ab5ef 100644 --- a/rups/src/proto/mod.rs +++ b/rups/src/proto/mod.rs @@ -328,7 +328,7 @@ impl_words! { /// Represents a variable. Var("VAR"), /// Client requests the server version. - Version("VERSION"), + Version("VER"), } use crate::{Error, NutError}; diff --git a/rups/src/proto/server.rs b/rups/src/proto/server.rs index 6f4b5dc..eecb482 100644 --- a/rups/src/proto/server.rs +++ b/rups/src/proto/server.rs @@ -481,7 +481,7 @@ mod tests { Sentences::QueryHelp {} ); test_encode_decode!( - ["VERSION"] <=> + ["VER"] <=> Sentences::QueryVersion {} ); test_encode_decode!(