use std::ffi::OsString;
use std::time;
use anyhow::anyhow;
use radicle::node::{Address, Node, NodeId, PeerAddr, ROUTING_DB_FILE, TRACKING_DB_FILE};
use radicle::prelude::Id;
use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
use crate::terminal::Element as _;
#[path = "node/control.rs"]
mod control;
#[path = "node/events.rs"]
mod events;
#[path = "node/routing.rs"]
mod routing;
#[path = "node/tracking.rs"]
mod tracking;
pub const HELP: Help = Help {
name: "node",
description: "Control and query the Radicle Node",
version: env!("CARGO_PKG_VERSION"),
usage: r#"
Usage
rad node status [...]
rad node start [--foreground] [--verbose] [ ...] [-- ...]
rad node stop [...]
rad node logs [-n ]
rad node connect @ [...]
rad node routing [--rid ] [--nid ] [--json] [...]
rad node tracking [--repos | --nodes] [ ...]
rad node events [--timeout ] [-n ] [...]
For `` see `radicle-node --help`.
Start options
--foreground Start the node in the foreground
--verbose, -v Verbose output
Routing options
--rid Show the routing table entries for the given RID
--nid Show the routing table entries for the given NID
--json Output the routing table as json
Tracking options
--repos Show the tracked repositories table
--nodes Show the tracked nodes table
Events options
--timeout How long to wait to receive an event before giving up
--count, -n Exit after events
General options
--help Print help
"#,
};
pub struct Options {
op: Operation,
}
pub enum Operation {
Connect {
addr: PeerAddr,
timeout: time::Duration,
},
Events {
timeout: time::Duration,
count: usize,
},
Routing {
json: bool,
rid: Option,
nid: Option,
},
Start {
foreground: bool,
verbose: bool,
options: Vec,
},
Logs {
lines: usize,
},
Status,
Sessions,
Stop,
Tracking {
mode: TrackingMode,
},
}
#[derive(Default)]
pub enum TrackingMode {
#[default]
Repos,
Nodes,
}
#[derive(Default, PartialEq, Eq)]
pub enum OperationName {
Connect,
Events,
Routing,
Logs,
Start,
#[default]
Status,
Sessions,
Stop,
Tracking,
}
impl Args for Options {
fn from_args(args: Vec) -> anyhow::Result<(Self, Vec)> {
use lexopt::prelude::*;
let mut foreground = false;
let mut options = vec![];
let mut parser = lexopt::Parser::from_args(args);
let mut op: Option = None;
let mut tracking_mode = TrackingMode::default();
let mut nid: Option = None;
let mut rid: Option = None;
let mut json: bool = false;
let mut addr: Option> = None;
let mut lines: usize = 10;
let mut count: usize = usize::MAX;
let mut timeout = time::Duration::MAX;
let mut verbose = false;
while let Some(arg) = parser.next()? {
match arg {
Long("help") | Short('h') => {
return Err(Error::Help.into());
}
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
"connect" => op = Some(OperationName::Connect),
"events" => op = Some(OperationName::Events),
"logs" => op = Some(OperationName::Logs),
"routing" => op = Some(OperationName::Routing),
"start" => op = Some(OperationName::Start),
"status" => op = Some(OperationName::Status),
"stop" => op = Some(OperationName::Stop),
"tracking" => op = Some(OperationName::Tracking),
"sessions" => op = Some(OperationName::Sessions),
unknown => anyhow::bail!("unknown operation '{}'", unknown),
},
Value(val) if matches!(op, Some(OperationName::Connect)) => {
addr = Some(val.parse()?);
}
Long("rid") if matches!(op, Some(OperationName::Routing)) => {
let val = parser.value()?;
rid = term::args::rid(&val).ok();
}
Long("nid") if matches!(op, Some(OperationName::Routing)) => {
let val = parser.value()?;
nid = term::args::nid(&val).ok();
}
Long("json") if matches!(op, Some(OperationName::Routing)) => json = true,
Long("timeout")
if op == Some(OperationName::Events) || op == Some(OperationName::Connect) =>
{
let val = parser.value()?;
timeout = term::args::seconds(&val)?;
}
Long("count") | Short('n') if matches!(op, Some(OperationName::Events)) => {
let val = parser.value()?;
count = term::args::number(&val)?;
}
Long("repos") if matches!(op, Some(OperationName::Tracking)) => {
tracking_mode = TrackingMode::Repos
}
Long("nodes") if matches!(op, Some(OperationName::Tracking)) => {
tracking_mode = TrackingMode::Nodes
}
Long("foreground") if matches!(op, Some(OperationName::Start)) => {
foreground = true;
}
Long("verbose") | Short('v') if matches!(op, Some(OperationName::Start)) => {
verbose = true;
}
Short('n') if matches!(op, Some(OperationName::Logs)) => {
lines = parser.value()?.parse()?;
}
Value(val) if matches!(op, Some(OperationName::Start)) => {
options.push(val);
}
_ => return Err(anyhow!(arg.unexpected())),
}
}
let op = match op.unwrap_or_default() {
OperationName::Connect => Operation::Connect {
addr: addr.ok_or_else(|| {
anyhow!("an address of the form `@:` must be provided")
})?,
timeout,
},
OperationName::Events => Operation::Events { timeout, count },
OperationName::Routing => Operation::Routing { rid, nid, json },
OperationName::Logs => Operation::Logs { lines },
OperationName::Start => Operation::Start {
foreground,
verbose,
options,
},
OperationName::Status => Operation::Status,
OperationName::Sessions => Operation::Sessions,
OperationName::Stop => Operation::Stop,
OperationName::Tracking => Operation::Tracking {
mode: tracking_mode,
},
};
Ok((Options { op }, vec![]))
}
}
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;
let mut node = Node::new(profile.socket());
match options.op {
Operation::Connect { addr, timeout } => {
control::connect(&mut node, addr.id, addr.addr, timeout)?
}
Operation::Sessions => {
let sessions = control::sessions(&node)?;
if let Some(table) = sessions {
table.print();
}
}
Operation::Events { timeout, count } => {
events::run(node, count, timeout)?;
}
Operation::Routing { rid, nid, json } => {
let store =
radicle::node::routing::Table::reader(profile.home.node().join(ROUTING_DB_FILE))?;
routing::run(&store, rid, nid, json)?;
}
Operation::Logs { lines } => control::logs(lines, Some(time::Duration::MAX), &profile)?,
Operation::Start {
foreground,
options,
verbose,
} => {
control::start(node, !foreground, verbose, options, &profile)?;
}
Operation::Status => {
control::status(&node, &profile)?;
}
Operation::Stop => {
control::stop(node)?;
}
Operation::Tracking { mode } => {
let store = radicle::node::tracking::store::Config::reader(
profile.home.node().join(TRACKING_DB_FILE),
)?;
tracking::run(&store, mode)?
}
}
Ok(())
}