add connect command

This commit is contained in:
Christien Rioux 2025-01-18 21:27:26 -05:00
parent 02c5f79285
commit 22634c48ec
6 changed files with 192 additions and 92 deletions

View File

@ -57,6 +57,7 @@ struct CommandProcessorInner {
#[derive(Clone)] #[derive(Clone)]
pub struct CommandProcessor { pub struct CommandProcessor {
inner: Arc<Mutex<CommandProcessorInner>>, inner: Arc<Mutex<CommandProcessorInner>>,
settings: Arc<Settings>,
} }
impl CommandProcessor { impl CommandProcessor {
@ -75,6 +76,7 @@ impl CommandProcessor {
last_call_id: None, last_call_id: None,
enable_app_messages: false, enable_app_messages: false,
})), })),
settings: Arc::new(settings.clone()),
} }
} }
pub fn set_client_api_connection(&self, capi: ClientApiConnection) { pub fn set_client_api_connection(&self, capi: ClientApiConnection) {
@ -186,6 +188,54 @@ Core Debug Commands:
Ok(()) Ok(())
} }
pub fn cmd_connect(&self, rest: Option<String>, 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> { pub fn cmd_debug(&self, command_line: String, callback: UICallback) -> Result<(), String> {
trace!("CommandProcessor::cmd_debug"); trace!("CommandProcessor::cmd_debug");
let capi = self.capi(); let capi = self.capi();
@ -331,6 +381,7 @@ Core Debug Commands:
"exit" => self.cmd_exit(callback), "exit" => self.cmd_exit(callback),
"quit" => self.cmd_exit(callback), "quit" => self.cmd_exit(callback),
"disconnect" => self.cmd_disconnect(callback), "disconnect" => self.cmd_disconnect(callback),
"connect" => self.cmd_connect(rest, callback),
"shutdown" => self.cmd_shutdown(callback), "shutdown" => self.cmd_shutdown(callback),
"change_log_level" => self.cmd_change_log_level(rest, callback), "change_log_level" => self.cmd_change_log_level(rest, callback),
"change_log_ignore" => self.cmd_change_log_ignore(rest, callback), "change_log_ignore" => self.cmd_change_log_ignore(rest, callback),

View File

@ -28,10 +28,11 @@ pub struct InteractiveUIInner {
#[derive(Clone)] #[derive(Clone)]
pub struct InteractiveUI { pub struct InteractiveUI {
inner: Arc<Mutex<InteractiveUIInner>>, inner: Arc<Mutex<InteractiveUIInner>>,
_settings: Arc<Settings>,
} }
impl InteractiveUI { impl InteractiveUI {
pub fn new(_settings: &Settings) -> (Self, InteractiveUISender) { pub fn new(settings: &Settings) -> (Self, InteractiveUISender) {
let (cssender, csreceiver) = flume::unbounded::<ConnectionState>(); let (cssender, csreceiver) = flume::unbounded::<ConnectionState>();
let term = Term::stdout(); let term = Term::stdout();
@ -45,9 +46,10 @@ impl InteractiveUI {
error: None, error: None,
done: Some(StopSource::new()), done: Some(StopSource::new()),
connection_state_receiver: csreceiver, connection_state_receiver: csreceiver,
log_enabled: false, log_enabled: true,
enable_color, enable_color,
})), })),
_settings: Arc::new(settings.clone()),
}; };
let ui_sender = InteractiveUISender { let ui_sender = InteractiveUISender {
@ -169,7 +171,6 @@ impl InteractiveUI {
eprintln!("Error: {:?}", e); eprintln!("Error: {:?}", e);
self.inner.lock().done.take(); self.inner.lock().done.take();
} }
self.inner.lock().log_enabled = true;
} }
} else if line == "log warn" { } else if line == "log warn" {
let opt_cmdproc = self.inner.lock().cmdproc.clone(); let opt_cmdproc = self.inner.lock().cmdproc.clone();
@ -181,7 +182,6 @@ impl InteractiveUI {
eprintln!("Error: {:?}", e); eprintln!("Error: {:?}", e);
self.inner.lock().done.take(); self.inner.lock().done.take();
} }
self.inner.lock().log_enabled = true;
} }
} else if line == "log info" { } else if line == "log info" {
let opt_cmdproc = self.inner.lock().cmdproc.clone(); let opt_cmdproc = self.inner.lock().cmdproc.clone();
@ -193,7 +193,6 @@ impl InteractiveUI {
eprintln!("Error: {:?}", e); eprintln!("Error: {:?}", e);
self.inner.lock().done.take(); self.inner.lock().done.take();
} }
self.inner.lock().log_enabled = true;
} }
} else if line == "log debug" || line == "log" { } else if line == "log debug" || line == "log" {
let opt_cmdproc = self.inner.lock().cmdproc.clone(); let opt_cmdproc = self.inner.lock().cmdproc.clone();
@ -205,6 +204,8 @@ impl InteractiveUI {
eprintln!("Error: {:?}", e); eprintln!("Error: {:?}", e);
self.inner.lock().done.take(); self.inner.lock().done.take();
} }
}
if line == "log" {
self.inner.lock().log_enabled = true; self.inner.lock().log_enabled = true;
} }
} else if line == "log trace" { } else if line == "log trace" {
@ -217,7 +218,6 @@ impl InteractiveUI {
eprintln!("Error: {:?}", e); eprintln!("Error: {:?}", e);
self.inner.lock().done.take(); self.inner.lock().done.take();
} }
self.inner.lock().log_enabled = true;
} }
} else if line == "log off" { } else if line == "log off" {
let opt_cmdproc = self.inner.lock().cmdproc.clone(); let opt_cmdproc = self.inner.lock().cmdproc.clone();
@ -229,9 +229,27 @@ impl InteractiveUI {
eprintln!("Error: {:?}", e); eprintln!("Error: {:?}", e);
self.inner.lock().done.take(); 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() { } 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(); let cmdproc = self.inner.lock().cmdproc.clone();
if let Some(cmdproc) = &cmdproc { if let Some(cmdproc) = &cmdproc {
if let Err(e) = cmdproc.run_command( if let Err(e) = cmdproc.run_command(

View File

@ -3,7 +3,7 @@
#![deny(unused_must_use)] #![deny(unused_must_use)]
#![recursion_limit = "256"] #![recursion_limit = "256"]
use crate::{settings::NamedSocketAddrs, tools::*, ui::*}; use crate::{tools::*, ui::*};
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use flexi_logger::*; use flexi_logger::*;
@ -37,7 +37,7 @@ struct CmdlineArgs {
ipc_path: Option<PathBuf>, ipc_path: Option<PathBuf>,
/// Subnode index to use when connecting /// Subnode index to use when connecting
#[arg(short('n'), long, default_value = "0")] #[arg(short('n'), long, default_value = "0")]
subnode_index: usize, subnode_index: u16,
/// Address to connect to /// Address to connect to
#[arg(long, short = 'a')] #[arg(long, short = 'a')]
address: Option<String>, address: Option<String>,
@ -47,9 +47,9 @@ struct CmdlineArgs {
/// Specify a configuration file to use /// Specify a configuration file to use
#[arg(short = 'c', long, value_name = "FILE")] #[arg(short = 'c', long, value_name = "FILE")]
config_file: Option<PathBuf>, config_file: Option<PathBuf>,
/// log level /// Log level for the CLI itself (not for the Veilid node)
#[arg(value_enum)] #[arg(long, value_enum)]
log_level: Option<LogLevel>, cli_log_level: Option<LogLevel>,
/// interactive /// interactive
#[arg(long, short = 'i', group = "execution_mode")] #[arg(long, short = 'i', group = "execution_mode")]
interactive: bool, interactive: bool,
@ -93,11 +93,11 @@ fn main() -> Result<(), String> {
.map_err(|e| format!("configuration is invalid: {}", e))?; .map_err(|e| format!("configuration is invalid: {}", e))?;
// Set config from command line // 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.level = settings::LogLevel::Debug;
settings.logging.terminal.enabled = true; 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.level = settings::LogLevel::Trace;
settings.logging.terminal.enabled = true; settings.logging.terminal.enabled = true;
} }
@ -248,59 +248,14 @@ fn main() -> Result<(), String> {
// Determine IPC path to try // Determine IPC path to try
let mut client_api_ipc_path = None; let mut client_api_ipc_path = None;
if enable_ipc { if enable_ipc {
cfg_if::cfg_if! { client_api_ipc_path = settings.resolve_ipc_path(args.ipc_path, args.subnode_index);
if #[cfg(windows)] { if client_api_ipc_path.is_some() {
if let Some(ipc_path) = args.ipc_path.or(settings.ipc_path.clone()) { enable_network = false;
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);
}
}
}
}
} }
} }
let mut client_api_network_addresses = None; let mut client_api_network_addresses = None;
if enable_network { if enable_network {
let args_address = if let Some(args_address) = args.address { client_api_network_addresses = settings.resolve_network_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());
}
} }
// Create command processor // Create command processor

View File

@ -1,5 +1,6 @@
use directories::*; use directories::*;
use crate::tools::*;
use serde_derive::*; use serde_derive::*;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::net::{SocketAddr, ToSocketAddrs}; 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 struct NamedSocketAddrs {
pub _name: String, pub _name: String,
pub addrs: Vec<SocketAddr>, pub addrs: Vec<SocketAddr>,
@ -148,26 +149,26 @@ impl<'de> serde::Deserialize<'de> for NamedSocketAddrs {
} }
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct Terminal { pub struct Terminal {
pub enabled: bool, pub enabled: bool,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct File { pub struct File {
pub enabled: bool, pub enabled: bool,
pub directory: String, pub directory: String,
pub append: bool, pub append: bool,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct Logging { pub struct Logging {
pub terminal: Terminal, pub terminal: Terminal,
pub file: File, pub file: File,
pub level: LogLevel, pub level: LogLevel,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct Colors { pub struct Colors {
pub background: String, pub background: String,
pub shadow: String, pub shadow: String,
@ -182,7 +183,7 @@ pub struct Colors {
pub highlight_text: String, pub highlight_text: String,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct LogColors { pub struct LogColors {
pub trace: String, pub trace: String,
pub debug: String, pub debug: String,
@ -191,7 +192,7 @@ pub struct LogColors {
pub error: String, pub error: String,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct Theme { pub struct Theme {
pub shadow: bool, pub shadow: bool,
pub borders: String, pub borders: String,
@ -199,24 +200,24 @@ pub struct Theme {
pub log_colors: LogColors, pub log_colors: LogColors,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct NodeLog { pub struct NodeLog {
pub scrollback: usize, pub scrollback: usize,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct CommandLine { pub struct CommandLine {
pub history_size: usize, pub history_size: usize,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct Interface { pub struct Interface {
pub theme: Theme, pub theme: Theme,
pub node_log: NodeLog, pub node_log: NodeLog,
pub command_line: CommandLine, pub command_line: CommandLine,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct Settings { pub struct Settings {
pub enable_ipc: bool, pub enable_ipc: bool,
pub ipc_path: Option<PathBuf>, pub ipc_path: Option<PathBuf>,
@ -229,6 +230,90 @@ pub struct Settings {
} }
impl Settings { impl Settings {
//////////////////////////////////////////////////////////////////////////////////
pub fn new(config_file: Option<&OsStr>) -> Result<Self, config::ConfigError> {
// 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<PathBuf>,
subnode_index: u16,
) -> Option<PathBuf> {
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<String>,
) -> Result<Option<Vec<SocketAddr>>, 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))] #[cfg_attr(windows, expect(dead_code))]
fn get_server_default_directory(subpath: &str) -> PathBuf { fn get_server_default_directory(subpath: &str) -> PathBuf {
#[cfg(unix)] #[cfg(unix)]
@ -284,21 +369,6 @@ impl Settings {
default_log_directory default_log_directory
} }
pub fn new(config_file: Option<&OsStr>) -> Result<Self, config::ConfigError> {
// 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] #[test]

View File

@ -33,10 +33,10 @@ use veilid_logs::*;
#[derive(Args, Debug, Clone)] #[derive(Args, Debug, Clone)]
#[group(multiple = false)] #[group(multiple = false)]
pub struct Logging { pub struct Logging {
/// Turn on debug logging on the terminal /// Turn on debug logging on the terminal and over the client api
#[arg(long)] #[arg(long)]
debug: bool, debug: bool,
/// Turn on trace logging on the terminal /// Turn on trace logging on the terminal and over the client api
#[arg(long)] #[arg(long)]
trace: bool, trace: bool,
} }
@ -217,10 +217,14 @@ fn main() -> EyreResult<()> {
if args.logging.debug { if args.logging.debug {
settingsrw.logging.terminal.enabled = true; settingsrw.logging.terminal.enabled = true;
settingsrw.logging.terminal.level = LogLevel::Debug; settingsrw.logging.terminal.level = LogLevel::Debug;
settingsrw.logging.api.enabled = true;
settingsrw.logging.api.level = LogLevel::Debug;
} }
if args.logging.trace { if args.logging.trace {
settingsrw.logging.terminal.enabled = true; settingsrw.logging.terminal.enabled = true;
settingsrw.logging.terminal.level = LogLevel::Trace; 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 { if let Some(subnode_index) = args.subnode_index {

View File

@ -42,7 +42,9 @@ fn io_error_kind_from_error<T>(e: io::Error) -> io::Result<NetworkResult<T>> {
io::ErrorKind::InvalidInput | io::ErrorKind::InvalidData => { io::ErrorKind::InvalidInput | io::ErrorKind::InvalidData => {
Ok(NetworkResult::InvalidMessage(e.to_string())) 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), _ => Err(e),
} }
} }