diff options
Diffstat (limited to 'mumctl/src/main.rs')
| -rw-r--r-- | mumctl/src/main.rs | 949 |
1 files changed, 460 insertions, 489 deletions
diff --git a/mumctl/src/main.rs b/mumctl/src/main.rs index a187a3a..29c9e44 100644 --- a/mumctl/src/main.rs +++ b/mumctl/src/main.rs @@ -1,21 +1,13 @@ -use clap::{App, AppSettings, Arg, Shell, SubCommand, ArgMatches}; use colored::Colorize; -use log::{Record, Level, Metadata, LevelFilter, error, warn}; -use mumlib::command::{Command, CommandResponse}; -use mumlib::config::{self, ServerConfig, Config}; -use mumlib::state::Channel; -use std::{io::{self, Read, BufRead, Write}, iter, fmt::{Display, Formatter}, os::unix::net::UnixStream}; +use log::*; +use mumlib::command::{Command as MumCommand, CommandResponse}; +use mumlib::config::{self, Config, ServerConfig}; +use mumlib::state::Channel as MumChannel; +use std::{fmt,io::{self, BufRead, Read, Write}, iter, os::unix::net::UnixStream}; +use structopt::{clap::Shell, StructOpt}; const INDENTATION: &str = " "; -macro_rules! error_if_err { - ($func:expr) => { - if let Err(e) = $func { - error!("{}", e); - } - }; -} - struct SimpleLogger; impl log::Log for SimpleLogger { @@ -32,7 +24,8 @@ impl log::Log for SimpleLogger { Level::Warn => "warning: ".yellow(), _ => "".normal(), }, - record.args()); + record.args() + ); } } @@ -41,519 +34,499 @@ impl log::Log for SimpleLogger { static LOGGER: SimpleLogger = SimpleLogger; -fn main() { - log::set_logger(&LOGGER) - .map(|()| log::set_max_level(LevelFilter::Info)).unwrap(); - let mut config = match config::read_default_cfg() { - Ok(c) => c, - Err(e) => { - error!("Couldn't read config: {}", e); - return; - } - }; +#[derive(Debug, StructOpt)] +struct Mum { + #[structopt(subcommand)] + command: Command, +} - let mut app = App::new("mumctl") - .setting(AppSettings::ArgRequiredElseHelp) - .version(env!("VERSION")) - .subcommand( - SubCommand::with_name("connect") - .about("Connect to a server") - .arg(Arg::with_name("host").required(true)) - .arg(Arg::with_name("username")) - .arg(Arg::with_name("password")) - .arg( - Arg::with_name("port") - .long("port") - .short("p") - .takes_value(true), - ), - ) - .subcommand( - SubCommand::with_name("disconnect") - .about("Disconnect from the currently connected server"), - ) - .subcommand( - SubCommand::with_name("server") - .setting(AppSettings::ArgRequiredElseHelp) - .about("Handle servers") - .subcommand( - SubCommand::with_name("config") - .about("Configure a saved server") - .arg(Arg::with_name("server_name")) - .arg(Arg::with_name("var_name")) - .arg(Arg::with_name("var_value")), - ) - .subcommand( - SubCommand::with_name("rename") - .about("Rename a saved server") - .arg(Arg::with_name("prev_name").required(true)) - .arg(Arg::with_name("next_name").required(true)), - ) - .subcommand( - SubCommand::with_name("add") - .about("Add a new saved server") - .arg(Arg::with_name("name").required(true)) - .arg(Arg::with_name("host").required(true)) - .arg( - Arg::with_name("port") - .long("port") - .takes_value(true) - .default_value("64738"), - ) - .arg( - Arg::with_name("username") - .long("username") - .takes_value(true), - ) - .arg( - Arg::with_name("password") - .long("password") - .takes_value(true), - ), - ) - .subcommand( - SubCommand::with_name("remove") - .about("Remove a saved server") - .arg(Arg::with_name("name").required(true)), - ) - .subcommand( - SubCommand::with_name("list") - .about("List saved servers and number of people connected"), - ), - ) - .subcommand( - SubCommand::with_name("channel") - .about("Handle channels in the connected server") - .setting(AppSettings::ArgRequiredElseHelp) - .subcommand( - SubCommand::with_name("list") - .about("List all channels") - .arg(Arg::with_name("short").long("short").short("s")), - ) - .subcommand( - SubCommand::with_name("connect") - .about("Connect to another channel") - .arg(Arg::with_name("channel").required(true)), - ), - ) - .subcommand(SubCommand::with_name("status").about("Show current status")) - .subcommand( - SubCommand::with_name("config") - .about("Change config values") - .arg(Arg::with_name("name").required(true)) - .arg(Arg::with_name("value").required(true)), - ) - .subcommand( - SubCommand::with_name("config-reload").about("Force a reload of the config file"), - ) - .subcommand( - SubCommand::with_name("completions") - .about("Generate CLI completions") - .arg(Arg::with_name("zsh").long("zsh")) - .arg(Arg::with_name("bash").long("bash")) - .arg(Arg::with_name("fish").long("fish")), - ) - .subcommand( - SubCommand::with_name("volume") - .about("Change volume of either you or someone else") - .subcommand( - SubCommand::with_name("set") - .arg(Arg::with_name("user").required(true)) - .arg(Arg::with_name("volume").required(true)), - ) - .arg(Arg::with_name("user").required(true)) - .setting(AppSettings::SubcommandsNegateReqs), - ) - .subcommand( - SubCommand::with_name("mute") - .about("Mute someone/yourself") - .arg(Arg::with_name("user")) - ) - .subcommand( - SubCommand::with_name("unmute") - .about("Unmute someone/yourself") - .arg(Arg::with_name("user")) - ) - .subcommand( - SubCommand::with_name("deafen") - .about("Deafen yourself") - ) - .subcommand( - SubCommand::with_name("undeafen") - .about("Undeafen yourself") - ); +#[derive(Debug, StructOpt)] +enum Command { + /// Connect to a server + Connect { + host: String, + username: Option<String>, + password: Option<String>, + #[structopt(short = "p", long = "port")] + port: Option<u16>, + }, + /// Disconnect from the currently connected server + Disconnect, + /// Handle servers + Server(Server), + /// Handle channels in the connected server + Channel(Channel), + /// Show current status + Status, + /// Change config values + Config { + key: String, + value: String, + }, + /// Reload the config file + ConfigReload, + /// Output CLI completions + Completions(Completions), + /// Change volume of either you or someone else + Volume { + user: String, + volume: Option<f32>, + }, + /// Mute someone/yourself + Mute { + user: Option<String>, + }, + /// Unmute someone/yourself + Unmute { + user: Option<String>, + }, + /// Deafen yourself + Deafen, + /// Undeafen yourself + Undeafen, +} + +#[derive(Debug, StructOpt)] +enum Server { + /// Configure a saved server + Config { + server_name: Option<String>, + key: Option<String>, + value: Option<String>, + }, + /// Rename a saved server + Rename { + old_name: String, + new_name: String + }, + /// Add a new saved server + Add { + name: String, + host: String, + #[structopt(short = "p", long = "port")] + port: Option<u16>, + username: Option<String>, + #[structopt(requires = "username")] + password: Option<String>, + }, + /// Remove a saved server + Remove { + name: String, + }, + /// List saved servers and number of people connected + List, +} - let matches = app.clone().get_matches(); +#[derive(Debug, StructOpt)] +enum Channel { + List { + #[structopt(short = "s", long = "short")] + short: bool, + }, + Connect { + name: String, + }, +} - if let Err(e) = process_matches(matches, &mut config, &mut app) { - error!("{}", e); +#[derive(Debug, StructOpt)] +enum Completions { + Zsh, + Bash, + Fish, +} + +#[derive(Debug)] +enum CliError { + NoUsername, + ConnectionError, + NoServerFound(String), + NotSet(String), + UseServerRename, + ConfigKeyNotFound(String), + ServerAlreadyExists(String), + NoServers, +} + +#[derive(Debug)] +struct Error(Box<dyn std::error::Error>); // new type since otherwise we'd get an impl conflict with impl<T> From<T> for T below + +impl<E> From<E> for Error +where + E: std::error::Error + fmt::Display + 'static, +{ + fn from(e: E) -> Self { + Error(Box::new(e)) } +} - if !config::cfg_exists() { - println!( - "Config file not found. Create one in {}? [Y/n]", - config::default_cfg_path().display(), - ); - let stdin = std::io::stdin(); - let response = stdin.lock().lines().next(); - if let Some(Ok(true)) = response.map(|e| e.map(|e| &e == "Y")) { - error_if_err!(config.write_default_cfg(true)); - } - } else { - error_if_err!(config.write_default_cfg(false)); +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) } } -fn process_matches(matches: ArgMatches, config: &mut Config, app: &mut App) -> Result<(), Error> { - if let Some(matches) = matches.subcommand_matches("connect") { - match_server_connect(matches, &config)?; - } else if let Some(_) = matches.subcommand_matches("disconnect") { - error_if_err!(send_command(Command::ServerDisconnect)?); - } else if let Some(matches) = matches.subcommand_matches("server") { - if let Some(matches) = matches.subcommand_matches("config") { - match_server_config(matches, config); - } else if let Some(matches) = matches.subcommand_matches("rename") { - match_server_rename(matches, config); - } else if let Some(matches) = matches.subcommand_matches("remove") { - match_server_remove(matches, config); - } else if let Some(matches) = matches.subcommand_matches("add") { - match_server_add(matches, config); - } else if let Some(_) = matches.subcommand_matches("list") { - if config.servers.is_empty() { - warn!("No servers in config"); +impl std::error::Error for CliError {} + +impl fmt::Display for CliError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CliError::NoUsername => { + write!(f, "No username specified") } - let query = config - .servers - .iter() - .map(|e| { - let response = send_command(Command::ServerStatus { - host: e.host.clone(), - port: e.port.unwrap_or(mumlib::DEFAULT_PORT), - }); - response.map(|f| (e, f)) - }) - .collect::<Result<Vec<_>, _>>()?; - for (server, response) in query.into_iter() - .filter(|e| e.1.is_ok()) - .map(|e| (e.0, e.1.unwrap().unwrap())) { - if let CommandResponse::ServerStatus { - users, max_users, .. - } = response - { - println!("{} [{}/{}]", server.name, users, max_users) - } else { - unreachable!() - } + CliError::ConnectionError => { + write!(f, "Unable to connect to mumd. Is mumd running?") + } + CliError::NoServerFound(s) => { + write!(f, "Server '{}' not found", s) + } + CliError::NotSet(s) => { + write!(f, "Key '{}' not set", s) + } + CliError::UseServerRename => { + write!(f, "Use 'server rename' instead") + } + CliError::ConfigKeyNotFound(s) => { + write!(f, "Key '{}' not found", s) + } + CliError::ServerAlreadyExists(s) => { + write!(f, "A server named '{}' already exists", s) + } + CliError::NoServers => { + write!(f, "No servers found") } } - } else if let Some(matches) = matches.subcommand_matches("channel") { - if let Some(_matches) = matches.subcommand_matches("list") { - match send_command(Command::ChannelList)? { - Ok(res) => match res { - Some(CommandResponse::ChannelList { channels }) => { - print_channel(&channels, 0); - } - _ => unreachable!(), - }, - Err(e) => error!("{}", e), + } +} + +fn main() { + log::set_logger(&LOGGER) + .map(|()| log::set_max_level(LevelFilter::Info)) + .unwrap(); + if let Err(e) = match_opt() { + error!("{}", e); + } +} +fn match_opt() -> Result<(), Error> { + let mut config = config::read_default_cfg()?; + + let opt = Mum::from_args(); + match opt.command { + Command::Connect { + host, + username, + password, + port, + } => { + let port = port.unwrap_or(mumlib::DEFAULT_PORT); + + let (host, username, password, port) = + match config.servers.iter().find(|e| e.name == host) { + Some(server) => ( + &server.host, + server + .username + .as_ref() + .or(username.as_ref()) + .ok_or(CliError::NoUsername)?, + server.password.as_ref().or(password.as_ref()), + server.port.unwrap_or(port), + ), + None => ( + &host, + username.as_ref().ok_or(CliError::NoUsername)?, + password.as_ref(), + port, + ), + }; + + let response = send_command(MumCommand::ServerConnect { + host: host.to_string(), + port, + username: username.to_string(), + password: password.map(|x| x.to_string()), + accept_invalid_cert: true, //TODO + })??; + if let Some(CommandResponse::ServerConnect { welcome_message }) = response { + println!("Connected to {}", host); + if let Some(message) = welcome_message { + println!("Welcome: {}", message); + } } - } else if let Some(matches) = matches.subcommand_matches("connect") { - error_if_err!(send_command(Command::ChannelJoin { - channel_identifier: matches.value_of("channel").unwrap().to_string() - })); } - } else if let Some(_) = matches.subcommand_matches("status") { - match send_command(Command::Status)? { - Ok(res) => match res { - Some(CommandResponse::Status { server_state }) => { - parse_status(&server_state); + Command::Disconnect => { + send_command(MumCommand::ServerDisconnect)??; + } + Command::Server(server_command) => { + match_server_command(server_command, &mut config)?; + } + Command::Channel(channel_command) => { + match channel_command { + Channel::List { short: _short } => { + //TODO actually use this + match send_command(MumCommand::ChannelList)?? { + Some(CommandResponse::ChannelList { channels }) => { + print_channel(&channels, 0); + } + _ => unreachable!("Response should only be a ChannelList"), + } + } + Channel::Connect { name } => { + send_command(MumCommand::ChannelJoin { + channel_identifier: name, + })??; } - _ => unreachable!(), - }, - Err(e) => error!("{}", e), + } } - } else if let Some(matches) = matches.subcommand_matches("config") { - let name = matches.value_of("name").unwrap(); - let value = matches.value_of("value").unwrap(); - match name { + Command::Status => match send_command(MumCommand::Status)?? { + Some(CommandResponse::Status { server_state }) => { + parse_state(&server_state); + } + _ => unreachable!("Response should only be a Status"), + }, + Command::Config { key, value } => match key.as_str() { "audio.input_volume" => { if let Ok(volume) = value.parse() { - error_if_err!(send_command(Command::InputVolumeSet(volume))?); + send_command(MumCommand::InputVolumeSet(volume))??; config.audio.input_volume = Some(volume); } } "audio.output_volume" => { if let Ok(volume) = value.parse() { - error_if_err!(send_command(Command::OutputVolumeSet(volume))?); + send_command(MumCommand::OutputVolumeSet(volume))??; config.audio.output_volume = Some(volume); } } _ => { - error!("unknown config value {}", name); + return Err(CliError::ConfigKeyNotFound(key))?; } + }, + Command::ConfigReload => { + send_command(MumCommand::ConfigReload)??; } - } else if matches.subcommand_matches("config-reload").is_some() { - error_if_err!(send_command(Command::ConfigReload)?); - } else if let Some(matches) = matches.subcommand_matches("completions") { - app.gen_completions_to( - "mumctl", - match matches.value_of("shell").unwrap_or("zsh") { - "bash" => Shell::Bash, - "fish" => Shell::Fish, - _ => Shell::Zsh, - }, - &mut io::stdout(), - ); - } else if let Some(matches) = matches.subcommand_matches("volume") { - if let Some(matches) = matches.subcommand_matches("set") { - let user = matches.value_of("user").unwrap(); - let volume = matches.value_of("volume").unwrap(); - if let Ok(val) = volume.parse() { - error_if_err!(send_command(Command::UserVolumeSet(user.to_string(), val))) + Command::Completions(completions) => { + Mum::clap().gen_completions_to( + "mumctl", + match completions { + Completions::Bash => Shell::Bash, + Completions::Fish => Shell::Fish, + _ => Shell::Zsh, + }, + &mut io::stdout(), + ); + } + Command::Volume { user, volume } => { + if let Some(volume) = volume { + send_command(MumCommand::UserVolumeSet(user, volume))??; } else { - error!("invalid volume value: {}", volume); + //TODO report volume of user + // needs work on mumd + todo!(); } - } else { - let _user = matches.value_of("user").unwrap(); - //TODO implement me - //needs work on mumd to implement } - } else if let Some(matches) = matches.subcommand_matches("mute") { - let command = if let Some(user) = matches.value_of("user") { - Command::MuteOther(user.to_string(), Some(true)) - } else { - Command::MuteSelf(Some(true)) - }; - if let Err(e) = send_command(command)? { - error!("{}", e); - } - } else if let Some(matches) = matches.subcommand_matches("unmute") { - let command = if let Some(user) = matches.value_of("user") { - Command::MuteOther(user.to_string(), Some(false)) - } else { - Command::MuteSelf(Some(false)) - }; - if let Err(e) = send_command(command)? { - error!("{}", e); + Command::Mute { user } => match user { + Some(user) => { + send_command(MumCommand::MuteOther(user, Some(true)))??; + } + None => { + send_command(MumCommand::MuteSelf(Some(true)))??; + } + }, + Command::Unmute { user } => match user { + Some(user) => { + send_command(MumCommand::MuteOther(user, Some(false)))??; + } + None => { + send_command(MumCommand::MuteSelf(Some(false)))??; + } + }, + Command::Deafen => { + send_command(MumCommand::DeafenSelf(Some(true)))??; } - } else if let Some(_) = matches.subcommand_matches("deafen") { - if let Err(e) = send_command(Command::DeafenSelf(Some(true)))? { - error!("{}", e); + Command::Undeafen => { + send_command(MumCommand::DeafenSelf(Some(false)))??; } - } else if let Some(_) = matches.subcommand_matches("undeafen") { - if let Err(e) = send_command(Command::DeafenSelf(Some(false)))? { - error!("{}", e); + } + + if !config::cfg_exists() { + println!( + "Config file not found. Create one in {}? [Y/n]", + config::default_cfg_path().display(), + ); + let stdin = std::io::stdin(); + let response = stdin.lock().lines().next(); + if let Some(Ok(true)) = response.map(|e| e.map(|e| &e == "Y")) { + config.write_default_cfg(true)?; } } else { - unreachable!(); + config.write_default_cfg(false)?; } - Ok(()) } -fn match_server_connect(matches: &clap::ArgMatches<'_>, config: &mumlib::config::Config) -> Result<(), Error> { - let host = matches.value_of("host").unwrap(); - let username = matches.value_of("username"); - let password = matches.value_of("password"); - let port = match matches.value_of("port").map(|e| e.parse()) { - None => Some(mumlib::DEFAULT_PORT), - Some(Err(_)) => None, - Some(Ok(v)) => Some(v), - }; - if let Some(port) = port { - let (host, port, username, password) = match config.servers.iter().find(|e| e.name == host) { - Some(server_config) => { - let host = server_config.host.as_str(); - let port = server_config.port.unwrap_or(port); - let username = server_config - .username - .as_deref() - .or(username); - if username.is_none() { - error!("no username specified"); - return Ok(()); //TODO? return as error - } - let password = server_config - .password - .as_deref() - .or(password); - (host, port, username.unwrap(), password) - } - None => { - if username.is_none() { - error!("no username specified"); - return Ok(()); //TODO? return as error - } - (host, port, username.unwrap(), password) - } - }; - let response = send_command(Command::ServerConnect { - host: host.to_string(), - port, - username: username.to_string(), - password: password.map(|x| x.to_string()), - accept_invalid_cert: true, //TODO - })? - .map(|e| (e, host)); - match response { - Ok((e, host)) => { - if let Some(CommandResponse::ServerConnect { welcome_message }) = e { - println!("Connected to {}", host); - if let Some(message) = welcome_message { - println!("Welcome: {}", message); +fn match_server_command(server_command: Server, config: &mut Config) -> Result<(), CliError> { + match server_command { + Server::Config { + server_name, + key, + value, + } => { + let server_name = match server_name { + Some(server_name) => server_name, + None => { + for server in config.servers.iter() { + println!("{}", server.name); } + return Ok(()); } - } - Err(e) => { - error!("{}", e); - } - }; - } - - Ok(()) -} + }; + let server = config + .servers + .iter_mut() + .find(|s| s.name == server_name) + .ok_or(CliError::NoServerFound(server_name))?; -fn match_server_config(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) { - if let Some(server_name) = matches.value_of("server_name") { - let server = config.servers.iter_mut().find(|s| s.name == server_name); - if let Some(server) = server { - if let Some(var_name) = matches.value_of("var_name") { - if let Some(var_value) = matches.value_of("var_value") { - // save var_value in var_name (if it is valid) - match var_name { - "name" => { - error!("use mumctl server rename instead!"); - } - "host" => { - server.host = var_value.to_string(); - } - "port" => { - server.port = Some(var_value.parse().unwrap()); - } - "username" => { - server.username = Some(var_value.to_string()); - } - "password" => { - server.password = Some(var_value.to_string()); //TODO ask stdin if empty - } - _ => { - error!("variable {} not found", var_name); - } - }; - } else { - // var_value is None - // print value of var_name + match (key.as_deref(), value) { + (None, _) => { + print!( + "{}{}{}{}", + format!("host: {}\n", server.host.to_string()), + server + .port + .map(|s| format!("port: {}\n", s)) + .unwrap_or_else(|| "".to_string()), + server + .username + .as_ref() + .map(|s| format!("username: {}\n", s)) + .unwrap_or_else(|| "".to_string()), + server + .password + .as_ref() + .map(|s| format!("password: {}\n", s)) + .unwrap_or_else(|| "".to_string()), + ); + } + (Some("name"), None) => { + println!("{}", server.name); + } + (Some("host"), None) => { + println!("{}", server.host); + } + (Some("port"), None) => { println!( "{}", - match var_name { - "name" => { - server.name.to_string() - } - "host" => { - server.host.to_string() - } - "port" => { - server - .port - .map(|s| s.to_string()) - .unwrap_or(format!("{} not set", "error:".red())) - } - "username" => { - server - .username - .as_ref() - .map(|s| s.to_string()) - .unwrap_or(format!("{} not set", "error:".red())) - } - "password" => { - server - .password - .as_ref() - .map(|s| s.to_string()) - .unwrap_or(format!("{} not set", "error:".red())) - } - _ => { - format!("{} unknown variable", "error:".red()) - } - } + server.port.ok_or(CliError::NotSet("port".to_string()))? ); } - } else { - // var_name is None - // print server config - print!( - "{}{}{}{}", - format!("host: {}\n", server.host.to_string()), - server - .port - .map(|s| format!("port: {}\n", s)) - .unwrap_or_else(|| "".to_string()), - server - .username - .as_ref() - .map(|s| format!("username: {}\n", s)) - .unwrap_or_else(|| "".to_string()), - server - .password - .as_ref() - .map(|s| format!("password: {}\n", s)) - .unwrap_or_else(|| "".to_string()), - ) + (Some("username"), None) => { + println!( + "{}", + server + .username + .as_ref() + .ok_or(CliError::NotSet("username".to_string()))? + ); + } + (Some("password"), None) => { + println!( + "{}", + server + .password + .as_ref() + .ok_or(CliError::NotSet("password".to_string()))? + ); + } + (Some("name"), Some(_)) => { + return Err(CliError::UseServerRename)?; + } + (Some("host"), Some(value)) => { + server.host = value; + } + (Some("port"), Some(value)) => { + server.port = Some(value.parse().unwrap()); + } + (Some("username"), Some(value)) => { + server.username = Some(value); + } + (Some("password"), Some(value)) => { + server.password = Some(value); + //TODO ask stdin if empty + } + (Some(_), _) => { + return Err(CliError::ConfigKeyNotFound(key.unwrap()))?; + } } - } else { - // server is None - error!("server {} not found", server_name); - } - } else { - for server in config.servers.iter() { - println!("{}", server.name); } - } -} - -fn match_server_rename(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) { - let prev_name = matches.value_of("prev_name").unwrap(); - let next_name = matches.value_of("next_name").unwrap(); - if let Some(server) = config.servers.iter_mut().find(|s| s.name == prev_name) { - server.name = next_name.to_string(); - } else { - error!("server {} not found", prev_name); - } -} - -fn match_server_remove(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) { - let name = matches.value_of("name").unwrap(); - match config.servers.iter().position(|server| server.name == name) { - Some(idx) => { - config.servers.remove(idx); - } - None => { - error!("server {} not found", name); + Server::Rename { old_name, new_name } => { + config + .servers + .iter_mut() + .find(|s| s.name == old_name) + .ok_or(CliError::NoServerFound(old_name))? + .name = new_name; } - }; -} - -fn match_server_add(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) { - let name = matches.value_of("name").unwrap().to_string(); - let host = matches.value_of("host").unwrap().to_string(); - // optional arguments map None to None - let port = matches.value_of("port").map(|s| s.parse().unwrap()); - let username = matches.value_of("username").map(|s| s.to_string()); - let password = matches.value_of("password").map(|s| s.to_string()); - if config.servers.iter().any(|s| s.name == name) { - error!("a server named {} already exists", name); - } else { - config.servers.push(ServerConfig { + Server::Add { name, host, port, username, password, - }); + } => { + if config.servers.iter().any(|s| s.name == name) { + return Err(CliError::ServerAlreadyExists(name))?; + } else { + config.servers.push(ServerConfig { + name, + host, + port, + username, + password, + }); + } + } + Server::Remove { name } => { + let idx = config + .servers + .iter() + .position(|s| s.name == name) + .ok_or(CliError::NoServerFound(name))?; + config.servers.remove(idx); + } + Server::List => { + if config.servers.is_empty() { + return Err(CliError::NoServers)?; + } + let query = config + .servers + .iter() + .map(|e| { + let response = send_command(MumCommand::ServerStatus { + host: e.host.clone(), + port: e.port.unwrap_or(mumlib::DEFAULT_PORT), + }); + response.map(|f| (e, f)) + }) + .collect::<Result<Vec<_>, _>>()?; + for (server, response) in query + .into_iter() + .filter(|e| e.1.is_ok()) + .map(|e| (e.0, e.1.unwrap().unwrap())) + { + if let CommandResponse::ServerStatus { + users, max_users, .. + } = response + { + println!("{} [{}/{}]", server.name, users, max_users) + } else { + unreachable!() + } + } + } } + Ok(()) } -fn parse_status(server_state: &mumlib::state::Server) { +fn parse_state(server_state: &mumlib::state::Server) { println!( "Connected to {} as {}", server_state.host, server_state.username @@ -579,19 +552,28 @@ fn parse_status(server_state: &mumlib::state::Server) { } } -fn send_command(command: Command) -> Result<mumlib::error::Result<Option<CommandResponse>>, crate::Error> { - let mut connection = UnixStream::connect(mumlib::SOCKET_PATH).map_err(|_| Error::ConnectionError)?; +fn send_command( + command: MumCommand, +) -> Result<mumlib::error::Result<Option<CommandResponse>>, CliError> { + let mut connection = + UnixStream::connect(mumlib::SOCKET_PATH).map_err(|_| CliError::ConnectionError)?; let serialized = bincode::serialize(&command).unwrap(); - connection.write(&(serialized.len() as u32).to_be_bytes()).map_err(|_| Error::ConnectionError)?; - connection.write(&serialized).map_err(|_| Error::ConnectionError)?; + connection + .write(&(serialized.len() as u32).to_be_bytes()) + .map_err(|_| CliError::ConnectionError)?; + connection + .write(&serialized) + .map_err(|_| CliError::ConnectionError)?; - connection.read_exact(&mut [0; 4]).map_err(|_| Error::ConnectionError)?; - bincode::deserialize_from(&mut connection).map_err(|_| Error::ConnectionError) + connection + .read_exact(&mut [0; 4]) + .map_err(|_| CliError::ConnectionError)?; + bincode::deserialize_from(&mut connection).map_err(|_| CliError::ConnectionError) } -fn print_channel(channel: &Channel, depth: usize) { +fn print_channel(channel: &MumChannel, depth: usize) { println!( "{}{}{}", iter::repeat(INDENTATION).take(depth).collect::<String>(), @@ -615,14 +597,3 @@ fn print_channel(channel: &Channel, depth: usize) { print_channel(child, depth + 1); } } - -#[derive(Debug)] -enum Error { - ConnectionError -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "Unable to connect to mumd. Is mumd running?") - } -} |
