add connect command

This commit is contained in:
Christien Rioux 2025-01-18 21:27:26 -05:00
parent 5f594e2aa7
commit 06b7b60fb7
6 changed files with 192 additions and 92 deletions

View File

@ -57,6 +57,7 @@ struct CommandProcessorInner {
#[derive(Clone)]
pub struct CommandProcessor {
inner: Arc<Mutex<CommandProcessorInner>>,
settings: Arc<Settings>,
}
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<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> {
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),

View File

@ -28,10 +28,11 @@ pub struct InteractiveUIInner {
#[derive(Clone)]
pub struct InteractiveUI {
inner: Arc<Mutex<InteractiveUIInner>>,
_settings: Arc<Settings>,
}
impl InteractiveUI {
pub fn new(_settings: &Settings) -> (Self, InteractiveUISender) {
pub fn new(settings: &Settings) -> (Self, InteractiveUISender) {
let (cssender, csreceiver) = flume::unbounded::<ConnectionState>();
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(

View File

@ -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<PathBuf>,
/// 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<String>,
@ -47,9 +47,9 @@ struct CmdlineArgs {
/// Specify a configuration file to use
#[arg(short = 'c', long, value_name = "FILE")]
config_file: Option<PathBuf>,
/// log level
#[arg(value_enum)]
log_level: Option<LogLevel>,
/// Log level for the CLI itself (not for the Veilid node)
#[arg(long, value_enum)]
cli_log_level: Option<LogLevel>,
/// 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

View File

@ -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<SocketAddr>,
@ -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<PathBuf>,
@ -229,6 +230,90 @@ pub struct 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))]
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<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]

View File

@ -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 {

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 => {
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),
}
}