diff --git a/veilid-cli/src/command_processor.rs b/veilid-cli/src/command_processor.rs index 98a6bf4b..b802dfd6 100644 --- a/veilid-cli/src/command_processor.rs +++ b/veilid-cli/src/command_processor.rs @@ -57,6 +57,7 @@ struct CommandProcessorInner { #[derive(Clone)] pub struct CommandProcessor { inner: Arc>, + settings: Arc, } impl CommandProcessor { @@ -75,6 +76,7 @@ impl CommandProcessor { last_call_id: None, enable_app_messages: false, })), + settings: Arc::new(settings.clone()), } } pub fn set_client_api_connection(&self, capi: ClientApiConnection) { @@ -186,6 +188,54 @@ Core Debug Commands: Ok(()) } + pub fn cmd_connect(&self, rest: Option, callback: UICallback) -> Result<(), String> { + trace!("CommandProcessor::cmd_connect"); + let capi = self.capi(); + let ui = self.ui_sender(); + + let this = self.clone(); + spawn_detached_local("cmd connect", async move { + capi.disconnect().await; + + if let Some(rest) = rest { + if let Ok(subnode_index) = u16::from_str(&rest) { + let ipc_path = this + .settings + .resolve_ipc_path(this.settings.ipc_path.clone(), subnode_index); + this.set_ipc_path(ipc_path); + this.set_network_address(None); + } else if let Some(ipc_path) = + this.settings.resolve_ipc_path(Some(rest.clone().into()), 0) + { + this.set_ipc_path(Some(ipc_path)); + this.set_network_address(None); + } else if let Ok(Some(network_address)) = + this.settings.resolve_network_address(Some(rest.clone())) + { + if let Some(addr) = network_address.first() { + this.set_network_address(Some(*addr)); + this.set_ipc_path(None); + } else { + ui.add_node_event( + Level::Error, + &format!("Invalid network address: {}", rest), + ); + } + } else { + ui.add_node_event( + Level::Error, + &format!("Invalid connection string: {}", rest), + ); + } + } + + this.start_connection(); + ui.send_callback(callback); + }); + + Ok(()) + } + pub fn cmd_debug(&self, command_line: String, callback: UICallback) -> Result<(), String> { trace!("CommandProcessor::cmd_debug"); let capi = self.capi(); @@ -331,6 +381,7 @@ Core Debug Commands: "exit" => self.cmd_exit(callback), "quit" => self.cmd_exit(callback), "disconnect" => self.cmd_disconnect(callback), + "connect" => self.cmd_connect(rest, callback), "shutdown" => self.cmd_shutdown(callback), "change_log_level" => self.cmd_change_log_level(rest, callback), "change_log_ignore" => self.cmd_change_log_ignore(rest, callback), diff --git a/veilid-cli/src/interactive_ui.rs b/veilid-cli/src/interactive_ui.rs index 34a1e623..427853fc 100644 --- a/veilid-cli/src/interactive_ui.rs +++ b/veilid-cli/src/interactive_ui.rs @@ -28,10 +28,11 @@ pub struct InteractiveUIInner { #[derive(Clone)] pub struct InteractiveUI { inner: Arc>, + _settings: Arc, } impl InteractiveUI { - pub fn new(_settings: &Settings) -> (Self, InteractiveUISender) { + pub fn new(settings: &Settings) -> (Self, InteractiveUISender) { let (cssender, csreceiver) = flume::unbounded::(); let term = Term::stdout(); @@ -45,9 +46,10 @@ impl InteractiveUI { error: None, done: Some(StopSource::new()), connection_state_receiver: csreceiver, - log_enabled: false, + log_enabled: true, enable_color, })), + _settings: Arc::new(settings.clone()), }; let ui_sender = InteractiveUISender { @@ -169,7 +171,6 @@ impl InteractiveUI { eprintln!("Error: {:?}", e); self.inner.lock().done.take(); } - self.inner.lock().log_enabled = true; } } else if line == "log warn" { let opt_cmdproc = self.inner.lock().cmdproc.clone(); @@ -181,7 +182,6 @@ impl InteractiveUI { eprintln!("Error: {:?}", e); self.inner.lock().done.take(); } - self.inner.lock().log_enabled = true; } } else if line == "log info" { let opt_cmdproc = self.inner.lock().cmdproc.clone(); @@ -193,7 +193,6 @@ impl InteractiveUI { eprintln!("Error: {:?}", e); self.inner.lock().done.take(); } - self.inner.lock().log_enabled = true; } } else if line == "log debug" || line == "log" { let opt_cmdproc = self.inner.lock().cmdproc.clone(); @@ -205,6 +204,8 @@ impl InteractiveUI { eprintln!("Error: {:?}", e); self.inner.lock().done.take(); } + } + if line == "log" { self.inner.lock().log_enabled = true; } } else if line == "log trace" { @@ -217,7 +218,6 @@ impl InteractiveUI { eprintln!("Error: {:?}", e); self.inner.lock().done.take(); } - self.inner.lock().log_enabled = true; } } else if line == "log off" { let opt_cmdproc = self.inner.lock().cmdproc.clone(); @@ -229,9 +229,27 @@ impl InteractiveUI { eprintln!("Error: {:?}", e); self.inner.lock().done.take(); } - self.inner.lock().log_enabled = false; } + } else if line == "log hide" || line == "log disable" { + self.inner.lock().log_enabled = false; + } else if line == "log show" || line == "log enable" { + self.inner.lock().log_enabled = true; } else if !line.is_empty() { + if line == "help" { + let _ = writeln!( + stdout, + r#" +Interactive Mode Commands: + help - Display this help + clear - Clear the screen + log [level] - Set the client api log level for the node to one of: error,warn,info,debug,trace,off + hide|disable - Turn off viewing the log without changing the log level for the node + show|enable - Turn on viewing the log without changing the log level for the node + - With no option, 'log' turns on viewing the log and sets the level to 'debug' +"# + ); + } + let cmdproc = self.inner.lock().cmdproc.clone(); if let Some(cmdproc) = &cmdproc { if let Err(e) = cmdproc.run_command( diff --git a/veilid-cli/src/main.rs b/veilid-cli/src/main.rs index 5ab103fd..fe540eb4 100644 --- a/veilid-cli/src/main.rs +++ b/veilid-cli/src/main.rs @@ -3,7 +3,7 @@ #![deny(unused_must_use)] #![recursion_limit = "256"] -use crate::{settings::NamedSocketAddrs, tools::*, ui::*}; +use crate::{tools::*, ui::*}; use clap::{Parser, ValueEnum}; use flexi_logger::*; @@ -37,7 +37,7 @@ struct CmdlineArgs { ipc_path: Option, /// Subnode index to use when connecting #[arg(short('n'), long, default_value = "0")] - subnode_index: usize, + subnode_index: u16, /// Address to connect to #[arg(long, short = 'a')] address: Option, @@ -47,9 +47,9 @@ struct CmdlineArgs { /// Specify a configuration file to use #[arg(short = 'c', long, value_name = "FILE")] config_file: Option, - /// log level - #[arg(value_enum)] - log_level: Option, + /// Log level for the CLI itself (not for the Veilid node) + #[arg(long, value_enum)] + cli_log_level: Option, /// interactive #[arg(long, short = 'i', group = "execution_mode")] interactive: bool, @@ -93,11 +93,11 @@ fn main() -> Result<(), String> { .map_err(|e| format!("configuration is invalid: {}", e))?; // Set config from command line - if let Some(LogLevel::Debug) = args.log_level { + if let Some(LogLevel::Debug) = args.cli_log_level { settings.logging.level = settings::LogLevel::Debug; settings.logging.terminal.enabled = true; } - if let Some(LogLevel::Trace) = args.log_level { + if let Some(LogLevel::Trace) = args.cli_log_level { settings.logging.level = settings::LogLevel::Trace; settings.logging.terminal.enabled = true; } @@ -248,59 +248,14 @@ fn main() -> Result<(), String> { // Determine IPC path to try let mut client_api_ipc_path = None; if enable_ipc { - cfg_if::cfg_if! { - if #[cfg(windows)] { - if let Some(ipc_path) = args.ipc_path.or(settings.ipc_path.clone()) { - if is_ipc_socket_path(&ipc_path) { - // try direct path - enable_network = false; - client_api_ipc_path = Some(ipc_path); - } else { - // try subnode index inside path - let ipc_path = ipc_path.join(args.subnode_index.to_string()); - if is_ipc_socket_path(&ipc_path) { - // subnode indexed path exists - enable_network = false; - client_api_ipc_path = Some(ipc_path); - } - } - } - } else { - if let Some(ipc_path) = args.ipc_path.or(settings.ipc_path.clone()) { - if is_ipc_socket_path(&ipc_path) { - // try direct path - enable_network = false; - client_api_ipc_path = Some(ipc_path); - } else if ipc_path.exists() && ipc_path.is_dir() { - // try subnode index inside path - let ipc_path = ipc_path.join(args.subnode_index.to_string()); - if is_ipc_socket_path(&ipc_path) { - // subnode indexed path exists - enable_network = false; - client_api_ipc_path = Some(ipc_path); - } - } - } - } + client_api_ipc_path = settings.resolve_ipc_path(args.ipc_path, args.subnode_index); + if client_api_ipc_path.is_some() { + enable_network = false; } } let mut client_api_network_addresses = None; if enable_network { - let args_address = if let Some(args_address) = args.address { - match NamedSocketAddrs::try_from(args_address) { - Ok(v) => Some(v), - Err(e) => { - return Err(format!("Invalid server address: {}", e)); - } - } - } else { - None - }; - if let Some(address_arg) = args_address.or(settings.address.clone()) { - client_api_network_addresses = Some(address_arg.addrs); - } else if let Some(address) = settings.address.clone() { - client_api_network_addresses = Some(address.addrs.clone()); - } + client_api_network_addresses = settings.resolve_network_address(args.address)?; } // Create command processor diff --git a/veilid-cli/src/settings.rs b/veilid-cli/src/settings.rs index e83cd915..ddde6bad 100644 --- a/veilid-cli/src/settings.rs +++ b/veilid-cli/src/settings.rs @@ -1,5 +1,6 @@ use directories::*; +use crate::tools::*; use serde_derive::*; use std::ffi::OsStr; use std::net::{SocketAddr, ToSocketAddrs}; @@ -118,7 +119,7 @@ pub fn convert_loglevel(log_level: LogLevel) -> log::LevelFilter { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct NamedSocketAddrs { pub _name: String, pub addrs: Vec, @@ -148,26 +149,26 @@ impl<'de> serde::Deserialize<'de> for NamedSocketAddrs { } } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct Terminal { pub enabled: bool, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct File { pub enabled: bool, pub directory: String, pub append: bool, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct Logging { pub terminal: Terminal, pub file: File, pub level: LogLevel, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct Colors { pub background: String, pub shadow: String, @@ -182,7 +183,7 @@ pub struct Colors { pub highlight_text: String, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct LogColors { pub trace: String, pub debug: String, @@ -191,7 +192,7 @@ pub struct LogColors { pub error: String, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct Theme { pub shadow: bool, pub borders: String, @@ -199,24 +200,24 @@ pub struct Theme { pub log_colors: LogColors, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct NodeLog { pub scrollback: usize, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct CommandLine { pub history_size: usize, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct Interface { pub theme: Theme, pub node_log: NodeLog, pub command_line: CommandLine, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct Settings { pub enable_ipc: bool, pub ipc_path: Option, @@ -229,6 +230,90 @@ pub struct Settings { } impl Settings { + ////////////////////////////////////////////////////////////////////////////////// + + pub fn new(config_file: Option<&OsStr>) -> Result { + // Load the default config + let mut cfg = load_default_config()?; + + // Merge in the config file if we have one + if let Some(config_file) = config_file { + let config_file_path = Path::new(config_file); + // If the user specifies a config file on the command line then it must exist + cfg = load_config(cfg, config_file_path)?; + } + + // Generate config + cfg.try_deserialize() + } + + pub fn resolve_ipc_path( + &self, + ipc_path: Option, + subnode_index: u16, + ) -> Option { + let mut client_api_ipc_path = None; + // Determine IPC path to try + cfg_if::cfg_if! { + if #[cfg(windows)] { + if let Some(ipc_path) = ipc_path.or(self.ipc_path.clone()) { + if is_ipc_socket_path(&ipc_path) { + // try direct path + enable_network = false; + client_api_ipc_path = Some(ipc_path); + } else { + // try subnode index inside path + let ipc_path = ipc_path.join(subnode_index.to_string()); + if is_ipc_socket_path(&ipc_path) { + // subnode indexed path exists + client_api_ipc_path = Some(ipc_path); + } + } + } + } else { + if let Some(ipc_path) = ipc_path.or(self.ipc_path.clone()) { + if is_ipc_socket_path(&ipc_path) { + // try direct path + client_api_ipc_path = Some(ipc_path); + } else if ipc_path.exists() && ipc_path.is_dir() { + // try subnode index inside path + let ipc_path = ipc_path.join(subnode_index.to_string()); + if is_ipc_socket_path(&ipc_path) { + // subnode indexed path exists + client_api_ipc_path = Some(ipc_path); + } + } + } + } + } + client_api_ipc_path + } + + pub fn resolve_network_address( + &self, + address: Option, + ) -> Result>, String> { + let mut client_api_network_addresses = None; + + let args_address = if let Some(args_address) = address { + match NamedSocketAddrs::try_from(args_address) { + Ok(v) => Some(v), + Err(e) => { + return Err(format!("Invalid server address: {}", e)); + } + } + } else { + None + }; + if let Some(address_arg) = args_address.or(self.address.clone()) { + client_api_network_addresses = Some(address_arg.addrs); + } else if let Some(address) = self.address.clone() { + client_api_network_addresses = Some(address.addrs.clone()); + } + Ok(client_api_network_addresses) + } + + //////////////////////////////////////////////////////////////////////////// #[cfg_attr(windows, expect(dead_code))] fn get_server_default_directory(subpath: &str) -> PathBuf { #[cfg(unix)] @@ -284,21 +369,6 @@ impl Settings { default_log_directory } - - pub fn new(config_file: Option<&OsStr>) -> Result { - // Load the default config - let mut cfg = load_default_config()?; - - // Merge in the config file if we have one - if let Some(config_file) = config_file { - let config_file_path = Path::new(config_file); - // If the user specifies a config file on the command line then it must exist - cfg = load_config(cfg, config_file_path)?; - } - - // Generate config - cfg.try_deserialize() - } } #[test] diff --git a/veilid-server/src/main.rs b/veilid-server/src/main.rs index e3a5d698..360d52b6 100644 --- a/veilid-server/src/main.rs +++ b/veilid-server/src/main.rs @@ -33,10 +33,10 @@ use veilid_logs::*; #[derive(Args, Debug, Clone)] #[group(multiple = false)] pub struct Logging { - /// Turn on debug logging on the terminal + /// Turn on debug logging on the terminal and over the client api #[arg(long)] debug: bool, - /// Turn on trace logging on the terminal + /// Turn on trace logging on the terminal and over the client api #[arg(long)] trace: bool, } @@ -217,10 +217,14 @@ fn main() -> EyreResult<()> { if args.logging.debug { settingsrw.logging.terminal.enabled = true; settingsrw.logging.terminal.level = LogLevel::Debug; + settingsrw.logging.api.enabled = true; + settingsrw.logging.api.level = LogLevel::Debug; } if args.logging.trace { settingsrw.logging.terminal.enabled = true; settingsrw.logging.terminal.level = LogLevel::Trace; + settingsrw.logging.api.enabled = true; + settingsrw.logging.api.level = LogLevel::Trace; } if let Some(subnode_index) = args.subnode_index { diff --git a/veilid-tools/src/network_result.rs b/veilid-tools/src/network_result.rs index e1cfdedb..1a922520 100644 --- a/veilid-tools/src/network_result.rs +++ b/veilid-tools/src/network_result.rs @@ -42,7 +42,9 @@ fn io_error_kind_from_error(e: io::Error) -> io::Result> { io::ErrorKind::InvalidInput | io::ErrorKind::InvalidData => { Ok(NetworkResult::InvalidMessage(e.to_string())) } - io::ErrorKind::AddrNotAvailable => Ok(NetworkResult::AlreadyExists(e)), + io::ErrorKind::AddrNotAvailable | io::ErrorKind::AddrInUse => { + Ok(NetworkResult::AlreadyExists(e)) + } _ => Err(e), } }