umshctl/command/
routes_cmd.rs1use anyhow::Result;
8
9use crate::command::{format_duration, values::KeyArg};
10use crate::output::{field, note, subfield};
11use crate::routes::{ROUTE_TTL, RouteCache};
12
13#[derive(Debug, clap::Subcommand)]
14pub enum RoutesOp {
15 Show {
17 #[arg(value_name = "KEY")]
18 key: KeyArg,
19 },
20 Clear {
25 #[arg(value_name = "KEY")]
26 key: Option<KeyArg>,
27 },
28}
29
30pub fn run(op: Option<RoutesOp>) -> Result<()> {
31 let mut cache = RouteCache::load();
32 match op {
33 None => list(&cache),
34 Some(RoutesOp::Show { key }) => show(&cache, umsh::core::PublicKey(key.0)),
35 Some(RoutesOp::Clear { key: Some(key) }) => {
36 let key = umsh::core::PublicKey(key.0);
37 if cache.remove(&key) {
38 println!("forgot the route to {key}");
39 } else {
40 println!("no route was remembered for {key}");
41 }
42 cache.store()
43 }
44 Some(RoutesOp::Clear { key: None }) => {
45 match cache.clear() {
46 0 => println!("no routes were remembered"),
47 1 => println!("forgot 1 route"),
48 count => println!("forgot {count} routes"),
49 }
50 cache.store()
51 }
52 }
53}
54
55fn list(cache: &RouteCache) -> Result<()> {
56 if cache.is_empty() {
57 println!("no routes remembered");
58 note("a route is learned from a reply, and remembered for a day");
59 return Ok(());
60 }
61 for (key, record) in cache.iter() {
64 println!("{key}");
65 subfield("route", crate::routes::describe(&record.route));
66 subfield("learned", ago(record.age()));
67 subfield("expires", within(record.age()));
68 }
69 Ok(())
70}
71
72fn show(cache: &RouteCache, key: umsh::core::PublicKey) -> Result<()> {
73 let Some(record) = cache.get(&key) else {
74 println!("no route remembered for {key}");
75 note("the next command to it discovers one, and this remembers it");
76 return Ok(());
77 };
78 println!("{key}");
79 field("route", crate::routes::describe(&record.route));
80 field("learned", ago(record.age()));
81 field("expires", within(record.age()));
82 Ok(())
83}
84
85fn ago(age: std::time::Duration) -> String {
86 format!(
87 "{} ago",
88 format_duration(age.as_secs().min(u32::MAX.into()) as u32)
89 )
90}
91
92fn within(age: std::time::Duration) -> String {
93 match ROUTE_TTL.checked_sub(age) {
94 Some(left) => format!("in {}", format_duration(left.as_secs() as u32)),
95 None => "now".to_string(),
96 }
97}