aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mumctl/src/main.rs311
-rw-r--r--mumd/Cargo.toml1
-rw-r--r--mumd/src/state.rs30
-rw-r--r--mumlib/Cargo.toml1
-rw-r--r--mumlib/src/command.rs1
-rw-r--r--mumlib/src/config.rs99
-rw-r--r--mumlib/src/lib.rs1
-rw-r--r--usage.org30
8 files changed, 391 insertions, 83 deletions
diff --git a/mumctl/src/main.rs b/mumctl/src/main.rs
index f4b8139..6e97296 100644
--- a/mumctl/src/main.rs
+++ b/mumctl/src/main.rs
@@ -2,6 +2,8 @@ use clap::{App, AppSettings, Arg, Shell, SubCommand};
use colored::Colorize;
use ipc_channel::ipc::{self, IpcSender};
use mumlib::command::{Command, CommandResponse};
+use mumlib::config;
+use mumlib::config::ServerConfig;
use mumlib::setup_logger;
use mumlib::state::Channel;
use std::{fs, io, iter};
@@ -18,6 +20,10 @@ macro_rules! err_print {
fn main() {
setup_logger(io::stderr(), true);
+ let mut config = config::read_default_cfg();
+ if config.is_none() {
+ println!("{} unable to find config file", "error:".red());
+ }
let mut app = App::new("mumctl")
.setting(AppSettings::ArgRequiredElseHelp)
@@ -26,59 +32,90 @@ fn main() {
.setting(AppSettings::ArgRequiredElseHelp)
.subcommand(
SubCommand::with_name("connect")
- .setting(AppSettings::ArgRequiredElseHelp)
- .arg(Arg::with_name("host").required(true).index(1))
- .arg(Arg::with_name("username").required(true).index(2))
- .arg(Arg::with_name("port").short("p").long("port").takes_value(true)),
- )
- .subcommand(SubCommand::with_name("disconnect")),
- )
+ .arg(Arg::with_name("host").required(true))
+ .arg(Arg::with_name("username").required(true))
+ .arg(Arg::with_name("port")
+ .long("port")
+ .short("p")
+ .takes_value(true)))
+ .subcommand(
+ SubCommand::with_name("disconnect"))
+ .subcommand(
+ SubCommand::with_name("config")
+ .arg(Arg::with_name("server_name").required(true))
+ .arg(Arg::with_name("var_name"))
+ .arg(Arg::with_name("var_value")))
+ .subcommand(
+ SubCommand::with_name("rename")
+ .arg(Arg::with_name("prev_name").required(true))
+ .arg(Arg::with_name("next_name").required(true)))
+ .subcommand(
+ SubCommand::with_name("add")
+ .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")
+ .arg(Arg::with_name("name").required(true))))
.subcommand(
SubCommand::with_name("channel")
.setting(AppSettings::ArgRequiredElseHelp)
.subcommand(
SubCommand::with_name("list")
- .arg(Arg::with_name("short").short("s").long("short")),
- )
+ .arg(Arg::with_name("short")
+ .long("short")
+ .short("s")))
.subcommand(
- SubCommand::with_name("connect").arg(Arg::with_name("channel").required(true)),
- ),
- )
- .subcommand(SubCommand::with_name("status"))
- .subcommand(SubCommand::with_name("config")
- .arg(Arg::with_name("name")
- .required(true))
- .arg(Arg::with_name("value")
- .required(true)))
+ SubCommand::with_name("connect")
+ .arg(Arg::with_name("channel").required(true))))
+ .subcommand(
+ SubCommand::with_name("status"))
+ .subcommand(
+ SubCommand::with_name("config")
+ .arg(Arg::with_name("name").required(true))
+ .arg(Arg::with_name("value").required(true)))
+ .subcommand(
+ SubCommand::with_name("config-reload"))
.subcommand(SubCommand::with_name("completions")
- .arg(Arg::with_name("zsh")
- .long("zsh"))
- .arg(Arg::with_name("bash")
- .long("bash"))
- .arg(Arg::with_name("fish")
- .long("fish")));
+ .arg(Arg::with_name("zsh")
+ .long("zsh"))
+ .arg(Arg::with_name("bash")
+ .long("bash"))
+ .arg(Arg::with_name("fish")
+ .long("fish")));
let matches = app.clone().get_matches();
if let Some(matches) = matches.subcommand_matches("server") {
if let Some(matches) = matches.subcommand_matches("connect") {
- let host = matches.value_of("host").unwrap();
- let username = matches.value_of("username").unwrap();
- let port = match matches.value_of("port").map(|e| e.parse()) {
- None => Some(64738),
- Some(Err(_)) => None,
- Some(Ok(v)) => Some(v),
- };
- if let Some(port) = port {
- err_print!(send_command(Command::ServerConnect {
- host: host.to_string(),
- port,
- username: username.to_string(),
- accept_invalid_cert: true, //TODO
- }));
- }
+ match_server_connect(matches);
} else if let Some(_) = matches.subcommand_matches("disconnect") {
err_print!(send_command(Command::ServerDisconnect));
+ } else if let Some(matches) = matches.subcommand_matches("config") {
+ if let Some(config) = &mut config {
+ match_server_config(matches, config);
+ }
+ } else if let Some(matches) = matches.subcommand_matches("rename") {
+ if let Some(config) = &mut config {
+ match_server_rename(matches, config);
+ }
+ } else if let Some(matches) = matches.subcommand_matches("remove") {
+ if let Some(config) = &mut config {
+ match_server_remove(matches, config);
+ }
+ } else if let Some(matches) = matches.subcommand_matches("add") {
+ if let Some(config) = &mut config {
+ match_server_add(matches, config);
+ }
}
} else if let Some(matches) = matches.subcommand_matches("channel") {
if let Some(_matches) = matches.subcommand_matches("list") {
@@ -96,34 +133,12 @@ fn main() {
channel_identifier: matches.value_of("channel").unwrap().to_string()
}));
}
- } else if let Some(_matches) = matches.subcommand_matches("status") {
+ } else if let Some(_) = matches.subcommand_matches("status") {
match send_command(Command::Status) {
Ok(res) => match res {
Some(CommandResponse::Status { server_state }) => {
- println!(
- "Connected to {} as {}",
- server_state.host, server_state.username
- );
- let own_channel = server_state
- .channels
- .iter()
- .find(|e| e.users.iter().any(|e| e.name == server_state.username))
- .unwrap();
- println!(
- "Currently in {} with {} other client{}:",
- own_channel.name,
- own_channel.users.len() - 1,
- if own_channel.users.len() == 2 {
- ""
- } else {
- "s"
- }
- );
- println!("{}{}", INDENTATION, own_channel.name);
- for user in &own_channel.users {
- println!("{}{}{}", INDENTATION, INDENTATION, user);
- }
- }
+ parse_status(&server_state);
+ },
_ => unreachable!(),
},
Err(e) => println!("{} {}", "error:".red(), e),
@@ -141,6 +156,8 @@ fn main() {
println!("{} Unknown config value {}", "error:".red(), name);
}
}
+ } else if matches.subcommand_matches("config-reload").is_some() {
+ send_command(Command::ConfigReload).unwrap();
} else if let Some(matches) = matches.subcommand_matches("completions") {
app.gen_completions_to(
"mumctl",
@@ -153,6 +170,172 @@ fn main() {
);
return;
};
+
+ if let Some(config) = config {
+ config.write_default_cfg();
+ }
+}
+
+fn match_server_connect(matches : &clap::ArgMatches<>) {
+ let host = matches.value_of("host").unwrap();
+ let username = matches.value_of("username").unwrap();
+ let port = match matches.value_of("port").map(|e| e.parse()) {
+ None => Some(64738),
+ Some(Err(_)) => None,
+ Some(Ok(v)) => Some(v),
+ };
+ if let Some(port) = port {
+ err_print!(send_command(Command::ServerConnect {
+ host: host.to_string(),
+ port,
+ username: username.to_string(),
+ accept_invalid_cert: true, //TODO
+ }));
+ }
+}
+
+fn match_server_config(matches: &clap::ArgMatches<>, config: &mut mumlib::config::Config) {
+ let server_name = matches.value_of("server_name").unwrap();
+ if let Some(servers) = &mut config.servers {
+ let server = 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" => {
+ println!("{} use mumctl server rename instead!", "error:".red());
+ },
+ "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
+ },
+ _ => {
+ println!("{} variable {} not found", "error:".red(), var_name);
+ },
+ };
+ } else { // var_value is None
+ // print value of var_name
+ 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()) },
+ });
+ }
+ } 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("".to_string()),
+ server.username.as_ref().map(|s| format!("username: {}\n", s)).unwrap_or("".to_string()),
+ server.password.as_ref().map(|s| format!("password: {}\n", s)).unwrap_or("".to_string()),
+ )
+ }
+ } else { // server is None
+ println!("{} server {} not found", "error:".red(), server_name);
+ }
+ } else { // servers is None
+ println!("{} no servers found in configuration", "error:".red());
+ }
+}
+
+fn match_server_rename(matches: &clap::ArgMatches<>, config: &mut mumlib::config::Config) {
+ if let Some(servers) = &mut config.servers {
+ let prev_name = matches.value_of("prev_name").unwrap();
+ let next_name = matches.value_of("next_name").unwrap();
+ if let Some(server) = servers
+ .iter_mut()
+ .find(|s| s.name == prev_name) {
+ server.name = next_name.to_string();
+ } else {
+ println!("{} server {} not found", "error:".red(), prev_name);
+ }
+ }
+}
+
+fn match_server_remove(matches: &clap::ArgMatches<>, config: &mut mumlib::config::Config) {
+ let name = matches.value_of("name").unwrap();
+ if let Some(servers) = &mut config.servers {
+ match servers.iter().position(|server| server.name == name) {
+ Some(idx) => {
+ servers.remove(idx);
+ },
+ None => {
+ println!("{} server {} not found", "error:".red(), name);
+ }
+ };
+ } else {
+ println!("{} no servers found in configuration", "error:".red());
+ }
+}
+
+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 let Some(servers) = &mut config.servers {
+ if servers.iter().any(|s| s.name == name) {
+ println!("{} a server named {} already exists", "error:".red(), name);
+ } else {
+ servers.push(ServerConfig {
+ name,
+ host,
+ port,
+ username,
+ password,
+ });
+ }
+ } else {
+ config.servers = Some(vec![ServerConfig {
+ name,
+ host,
+ port,
+ username,
+ password,
+ }]);
+ }
+}
+
+fn parse_status(server_state: &mumlib::state::Server) {
+ println!(
+ "Connected to {} as {}",
+ server_state.host, server_state.username
+ );
+ let own_channel = server_state
+ .channels
+ .iter()
+ .find(|e| e.users.iter().any(|e| e.name == server_state.username))
+ .unwrap();
+ println!(
+ "Currently in {} with {} other client{}:",
+ own_channel.name,
+ own_channel.users.len() - 1,
+ if own_channel.users.len() == 2 {
+ ""
+ } else {
+ "s"
+ }
+ );
+ println!("{}{}", INDENTATION, own_channel.name);
+ for user in &own_channel.users {
+ println!("{}{}{}", INDENTATION, INDENTATION, user);
+ }
}
fn send_command(command: Command) -> mumlib::error::Result<Option<CommandResponse>> {
diff --git a/mumd/Cargo.toml b/mumd/Cargo.toml
index 9101b43..ffb463a 100644
--- a/mumd/Cargo.toml
+++ b/mumd/Cargo.toml
@@ -27,6 +27,5 @@ tokio = { version = "0.2", features = ["full"] }
tokio-tls = "0.3"
tokio-util = { version = "0.3", features = ["codec", "udp"] }
-#clap = "2.33"
#compressor = "0.3"
#daemonize = "0.4"
diff --git a/mumd/src/state.rs b/mumd/src/state.rs
index 55fd8ae..0dbf9c5 100644
--- a/mumd/src/state.rs
+++ b/mumd/src/state.rs
@@ -6,6 +6,7 @@ use mumble_protocol::control::msgs;
use mumble_protocol::control::ControlPacket;
use mumble_protocol::voice::Serverbound;
use mumlib::command::{Command, CommandResponse};
+use mumlib::config::Config;
use mumlib::error::{ChannelIdentifierError, Error};
use serde::{Deserialize, Serialize};
use std::collections::hash_map::Entry;
@@ -21,6 +22,7 @@ pub enum StatePhase {
}
pub struct State {
+ config: Option<Config>,
server: Option<Server>,
audio: Audio,
@@ -35,13 +37,17 @@ impl State {
packet_sender: mpsc::UnboundedSender<ControlPacket<Serverbound>>,
connection_info_sender: watch::Sender<Option<ConnectionInfo>>,
) -> Self {
- Self {
+ let audio = Audio::new();
+ let mut state = Self {
+ config: mumlib::config::read_default_cfg(),
server: None,
- audio: Audio::new(),
+ audio,
packet_sender,
connection_info_sender,
phase_watcher: watch::channel(StatePhase::Disconnected),
- }
+ };
+ state.reload_config();
+ state
}
//TODO? move bool inside Result
@@ -165,6 +171,10 @@ impl State {
self.audio.set_input_volume(volume);
(false, Ok(None))
}
+ Command::ConfigReload => {
+ self.reload_config();
+ (false, Ok(None))
+ }
}
}
@@ -197,6 +207,20 @@ impl State {
self.server.as_mut().unwrap().parse_user_state(msg);
}
+ pub fn reload_config(&mut self) {
+ if let Some(config) = mumlib::config::read_default_cfg() {
+ self.config = Some(config);
+ let config = &self.config.as_ref().unwrap();
+ if let Some(audio_config) = &config.audio {
+ if let Some(input_volume) = audio_config.input_volume {
+ self.audio.set_input_volume(input_volume);
+ }
+ }
+ } else {
+ warn!("config file not found");
+ }
+ }
+
pub fn initialized(&self) {
self.phase_watcher
.0
diff --git a/mumlib/Cargo.toml b/mumlib/Cargo.toml
index a2627d4..471a1fe 100644
--- a/mumlib/Cargo.toml
+++ b/mumlib/Cargo.toml
@@ -13,3 +13,4 @@ fern = "0.5"
log = "0.4"
mumble-protocol = "0.3"
serde = { version = "1.0", features = ["derive"] }
+toml = "0.5"
diff --git a/mumlib/src/command.rs b/mumlib/src/command.rs
index b4ab07a..05702f0 100644
--- a/mumlib/src/command.rs
+++ b/mumlib/src/command.rs
@@ -8,6 +8,7 @@ pub enum Command {
channel_identifier: String,
},
ChannelList,
+ ConfigReload,
InputVolumeSet(f32),
ServerConnect {
host: String,
diff --git a/mumlib/src/config.rs b/mumlib/src/config.rs
new file mode 100644
index 0000000..aa8a8ed
--- /dev/null
+++ b/mumlib/src/config.rs
@@ -0,0 +1,99 @@
+use log::*;
+use serde::{Deserialize, Serialize};
+use std::convert::TryFrom;
+use std::fs;
+use toml::Value;
+use toml::value::Array;
+
+#[derive(Debug, Deserialize, Serialize)]
+struct TOMLConfig {
+ audio: Option<AudioConfig>,
+ servers: Option<Array>,
+}
+
+#[derive(Clone, Debug)]
+pub struct Config {
+ pub audio: Option<AudioConfig>,
+ pub servers: Option<Vec<ServerConfig>>,
+}
+
+impl Config {
+ pub fn write_default_cfg(&self) -> Result<(), std::io::Error> {
+ let path = get_cfg_path();
+ let path = std::path::Path::new(&path);
+ // Possible race here. It's fine since it shows when:
+ // 1) the file doesn't exist when checked and is then created
+ // 2) the file exists when checked but is then removed
+ // If 1) we don't do anything anyway so it's fine, and if 2) we
+ // immediately re-create the file which, while not perfect, at least
+ // should work. Unless the file is removed AND the permissions
+ // change, but then we don't have permissions so we can't
+ // do anything anyways.
+ if !path.exists() {
+ warn!("config file {} does not exist, ignoring", path.display());
+ Ok(())
+ } else {
+ fs::write(path, toml::to_string(&TOMLConfig::from(self.clone())).unwrap())
+ }
+ }
+}
+
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct AudioConfig {
+ pub input_volume: Option<f32>,
+}
+
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct ServerConfig {
+ pub name: String,
+ pub host: String,
+ pub port: Option<u16>,
+ pub username: Option<String>,
+ pub password: Option<String>,
+}
+
+fn get_cfg_path() -> String {
+ ".mumdrc".to_string() //TODO XDG_CONFIG and whatever
+}
+
+impl TryFrom<TOMLConfig> for Config {
+ type Error = toml::de::Error;
+
+ fn try_from(config: TOMLConfig) -> Result<Self, Self::Error> {
+ Ok(Config {
+ audio: config.audio,
+ servers: config.servers.map(|servers| servers
+ .into_iter()
+ .map(|s| s.try_into::<ServerConfig>())
+ .collect())
+ .transpose()?,
+ })
+ }
+}
+
+impl From<Config> for TOMLConfig {
+ fn from(config: Config) -> Self {
+ TOMLConfig {
+ audio: config.audio,
+ servers: config.servers.map(|servers| servers
+ .into_iter()
+ .map(|s| Value::try_from::<ServerConfig>(s).unwrap())
+ .collect()),
+ }
+ }
+}
+
+pub fn read_default_cfg() -> Option<Config> {
+ Some(Config::try_from(
+ toml::from_str::<TOMLConfig>(
+ &match fs::read_to_string(get_cfg_path()) {
+ Ok(f) => {
+ f.to_string()
+ },
+ Err(_) => {
+ return None
+ }
+ }
+ ).expect("invalid TOML in config file") //TODO
+ ).expect("invalid config in TOML")) //TODO
+}
diff --git a/mumlib/src/lib.rs b/mumlib/src/lib.rs
index b26db13..93b7682 100644
--- a/mumlib/src/lib.rs
+++ b/mumlib/src/lib.rs
@@ -1,4 +1,5 @@
pub mod command;
+pub mod config;
pub mod error;
pub mod state;
diff --git a/usage.org b/usage.org
index 8746014..9402f39 100644
--- a/usage.org
+++ b/usage.org
@@ -16,7 +16,7 @@ We want to support / explain how to do the following at some point:
The daemon doesn't do anything by itself. Interfacing with it is done through
`mumctl`.
-* Basic commands
+* 0.1
The basic commands are the smallest subset of commands that allow the user to
actually use mum for something. In this case it means connecting to a server,
listing channels and connecting to channels.
@@ -85,17 +85,17 @@ your_name@localhost:65387/root (3)
your_name
#+END_SRC
-* More commands
+* 0.2
** server
-*** add
-Add a server with a name:
+*** TODO add
+**** DONE With name
#+BEGIN_SRC bash
-$ mumctl server add 127.0.0.1 loopback
+$ mumctl server add loopback 127.0.0.1
username: ***
password: ***
#+END_SRC
-Add a server without a name:
+**** TODO Without name
#+BEGIN_SRC bash
$ mumctl server add 127.0.0.1
username: ***
@@ -110,22 +110,22 @@ loopback [4 / 100]
127.0.0.1 [4 / 100]
127.0.0.3 [OFFLINE]
#+END_SRC
-*** config
-**** username
+*** TODO config
+**** DONE username
#+BEGIN_SRC bash
-$ mumctl server config loopback set username xX_gamerboy_Xx
+$ mumctl server config loopback username xX_gamerboy_Xx
#+END_SRC
-**** password
+**** TODO password
#+BEGIN_SRC bash
-$ mumctl server config loopback set password ***
+$ mumctl server config loopback password ***
#+END_SRC
Optionally ask stdin
#+BEGIN_SRC bash
-$ mumctl server config loopback set password
+$ mumctl server config loopback password
enter password: ***
#+END_SRC
-*** connect: handle invalid keys
+*** TODO connect: handle invalid keys
#+BEGIN_SRC bash
server offered invalid key. what do you want to do?
[I]nspect, [A]ccept, [D]eny, [C]ompare, [T]emporarily trust (default D):
@@ -135,11 +135,11 @@ server offered invalid key. what do you want to do?
- Deny: Abort the connection. Do not trust the key.
- Compare: Compare the key to a file to confirm legitimacy and ask again.
- Temporarily trust: Accept the key and connect, but do not trust the key.
-*** rename
+*** DONE rename
#+BEGIN_SRC bash
$ mumctl server rename loopback my_server
#+END_SRC
-** config
+** TODO config
#+BEGIN_SRC bash
$ mumctl config audio.input_volume 1.1
$ mumctl config audio.input_volume