diff options
Diffstat (limited to 'src/config/mod.rs')
| -rw-r--r-- | src/config/mod.rs | 131 |
1 files changed, 78 insertions, 53 deletions
diff --git a/src/config/mod.rs b/src/config/mod.rs index 538689e..0aaccd6 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -16,6 +16,7 @@ mod import; use std::collections::BTreeMap; use std::net::SocketAddr; use std::path::{Path, PathBuf}; +use std::time::Duration; use anyhow::{Context, Result, anyhow, bail}; use clap::parser::ValueSource; @@ -33,6 +34,10 @@ pub(crate) enum Kind { Flag, Text, Integer, + /// Seconds, or with units: `500ms`, `30s`, `1h30m` (`units::parse_duration`). + Duration, + /// Bytes, or with a unit: `64KiB` (`units::parse_size`). + Size, /// Comma- or line-separated; repeatable on the command line. List, } @@ -138,20 +143,20 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[ Kind::Text, "PATH", None, - "This node's document on disk. Read at start: the node resumes with it, ready at once. Kept up to date while ready: changes are appended every --document-append-secs, and the file is rewritten after a switch, a rejoin or a restart, and when the appended changes outgrew it.", + "This node's document on disk. Read at start: the node resumes with it, ready at once. Kept up to date while ready: changes are appended every --document-append, and the file is rewritten after a switch, a rejoin or a restart, and when the appended changes outgrew it.", ), option( - "document-append-secs", - Kind::Integer, - "SECONDS", - Some("5"), + "document-append", + Kind::Duration, + "DURATION", + Some("5s"), "How often changes are appended to the document file.", ), option( - "document-rewrite-secs", - Kind::Integer, - "SECONDS", - Some("3600"), + "document-rewrite", + Kind::Duration, + "DURATION", + Some("1h"), "Shortest time between rewrites of the document file because its appended changes outgrew its full save (a new epoch or a restart rewrites it at once).", ), option( @@ -176,10 +181,10 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[ "A name for this node, shown next to its public key (/peers, metrics). Published in the swarm's member list; not unique, never an identity.", ), option( - "keepalive-secs", - Kind::Integer, - "SECONDS", - Some("60"), + "keepalive", + Kind::Duration, + "DURATION", + Some("1m"), "Keepalive interval; dormancy, resync and probe timings derive from it.", ), option( @@ -190,18 +195,18 @@ pub(crate) const OPTIONS: &[OptionSpec] = &[ "Neighbours each node keeps (at least 2). Swarms of up to this many + 1 nodes are a full mesh; larger ones sync over several hops.", ), option( - "electorate-window-days", - Kind::Integer, - "DAYS", - Some("7"), - "Members that renewed their alive stamp within this many days vote on remakes (a majority of them must agree). Matters on the node that coordinates a remake.", + "electorate-window", + Kind::Duration, + "DURATION", + Some("7d"), + "Members that renewed their alive stamp within this time vote on remakes (a majority of them must agree). Matters on the node that coordinates a remake.", ), option( - "blob-chunk-kib", - Kind::Integer, - "KIB", - Some("64"), - "Chunk size when serving large transfers to other nodes (e.g. an epoch's snapshot to a rejoining node), in KiB (1 to 4096).", + "blob-chunk", + Kind::Size, + "SIZE", + Some("64KiB"), + "Chunk size when serving large transfers to other nodes (e.g. an epoch's snapshot to a rejoining node), 1KiB to 4MiB.", ), option( "mycelium-rest-url", @@ -276,24 +281,34 @@ pub(crate) struct Settings { pub(crate) genesis: bool, pub(crate) state_file: Option<PathBuf>, pub(crate) document_file: Option<PathBuf>, - #[serde(default = "default_document_append_secs")] - pub(crate) document_append_secs: u64, - #[serde(default = "default_document_rewrite_secs")] - pub(crate) document_rewrite_secs: u64, + #[serde( + default = "default_document_append", + with = "crate::units::duration_option" + )] + pub(crate) document_append: Duration, + #[serde( + default = "default_document_rewrite", + with = "crate::units::duration_option" + )] + pub(crate) document_rewrite: Duration, pub(crate) recover_from: Option<PathBuf>, pub(crate) remake: Option<String>, #[serde(default)] pub(crate) force: Vec<String>, pub(crate) swarm_key_file: Option<PathBuf>, pub(crate) alias: Option<String>, - #[serde(default = "default_keepalive_secs")] - pub(crate) keepalive_secs: u64, + #[serde(default = "default_keepalive", with = "crate::units::duration_option")] + pub(crate) keepalive: Duration, #[serde(default = "default_neighbours")] pub(crate) neighbours: usize, - #[serde(default = "default_electorate_window_days")] - pub(crate) electorate_window_days: u64, - #[serde(default = "default_blob_chunk_kib")] - pub(crate) blob_chunk_kib: u32, + #[serde( + default = "default_electorate_window", + with = "crate::units::duration_option" + )] + pub(crate) electorate_window: Duration, + /// In bytes. + #[serde(default = "default_blob_chunk", with = "crate::units::size_option")] + pub(crate) blob_chunk: u64, pub(crate) mycelium_rest_url: Option<String>, pub(crate) mycelium_key_file: Option<PathBuf>, pub(crate) mycelium_network_key_file: Option<PathBuf>, @@ -307,23 +322,23 @@ pub(crate) struct Settings { fn default_bind_addr() -> SocketAddr { SocketAddr::from(([127, 0, 0, 1], 3000)) } -fn default_document_append_secs() -> u64 { - 5 +fn default_document_append() -> Duration { + Duration::from_secs(5) } -fn default_document_rewrite_secs() -> u64 { - 3600 +fn default_document_rewrite() -> Duration { + Duration::from_secs(3600) } -fn default_keepalive_secs() -> u64 { - 60 +fn default_keepalive() -> Duration { + Duration::from_secs(60) } fn default_neighbours() -> usize { 12 } -fn default_electorate_window_days() -> u64 { - 7 +fn default_electorate_window() -> Duration { + Duration::from_secs(7 * 24 * 3600) } -fn default_blob_chunk_kib() -> u32 { - 64 +fn default_blob_chunk() -> u64 { + 64 * 1024 } fn default_mycelium_peers() -> Vec<String> { vec!["tls://127.0.0.1:9651".to_owned()] @@ -383,17 +398,17 @@ impl Settings { if self.neighbours < 2 { bail!("neighbours must be at least 2"); } - if self.document_append_secs == 0 || self.document_rewrite_secs == 0 { - bail!("document-append-secs and document-rewrite-secs must be at least 1"); + if self.document_append.is_zero() || self.document_rewrite.is_zero() { + bail!("document-append and document-rewrite must be longer than 0"); } - if self.keepalive_secs == 0 { - bail!("keepalive-secs must be at least 1"); + if self.keepalive < Duration::from_millis(100) { + bail!("keepalive must be at least 100ms"); } - if !(1..=4096).contains(&self.blob_chunk_kib) { - bail!("blob-chunk-kib must be between 1 and 4096"); + if !(1024..=4 << 20).contains(&self.blob_chunk) { + bail!("blob-chunk must be between 1KiB and 4MiB"); } - if self.electorate_window_days == 0 { - bail!("electorate-window-days must be at least 1"); + if self.electorate_window.is_zero() { + bail!("electorate-window must be longer than 0"); } if let Some(alias) = &self.alias { check_alias(alias)?; @@ -598,6 +613,14 @@ fn from_text(kind: Kind, text: &str) -> Result<JsonValue> { .map_err(|_| anyhow!("{text:?} is not a non-negative integer"))?; JsonValue::from(number) } + Kind::Duration => { + crate::units::parse_duration(text).map_err(|reason| anyhow!(reason))?; + JsonValue::String(text.trim().to_owned()) + } + Kind::Size => { + crate::units::parse_size(text).map_err(|reason| anyhow!(reason))?; + JsonValue::String(text.trim().to_owned()) + } Kind::Flag => match text.trim() { "true" | "yes" | "1" => JsonValue::Bool(true), "false" | "no" | "0" => JsonValue::Bool(false), @@ -719,7 +742,9 @@ fn command() -> Command { command = command.arg(match spec.kind { Kind::Flag => arg.action(ArgAction::SetTrue), Kind::List => arg.value_name(spec.value_name).action(ArgAction::Append), - Kind::Text | Kind::Integer => arg.value_name(spec.value_name).action(ArgAction::Set), + Kind::Text | Kind::Integer | Kind::Duration | Kind::Size => { + arg.value_name(spec.value_name).action(ArgAction::Set) + } }); } command @@ -1089,7 +1114,7 @@ mod tests { assert!(resolve(layer(json!({ "htpasswd": "u", "alias": " " })), Vec::new()).is_err()); assert!( resolve( - layer(json!({ "htpasswd": "u", "keepalive-secs": "x" })), + layer(json!({ "htpasswd": "u", "keepalive": "x" })), Vec::new() ) .is_err() |
