cli: Add `--inventory` mode to `rad sync`

This mode allows to announce our inventory. It's not normally needed,
but may be suggested by the `rad publish` command in certain cases.
This commit is contained in:
cloudhead 2023-09-03 16:59:22 +02:00
parent 8199f1a679
commit 9de10ade5a
No known key found for this signature in database
4 changed files with 116 additions and 60 deletions

View File

@ -224,7 +224,7 @@ pub fn clone<G: Signer>(
let results = sync::fetch( let results = sync::fetch(
id, id,
sync::SyncMode::default(), sync::RepoSync::default(),
time::Duration::from_secs(9), time::Duration::from_secs(9),
node, node,
)?; )?;

View File

@ -19,9 +19,8 @@ pub const HELP: Help = Help {
usage: r#" usage: r#"
Usage Usage
rad sync [<rid>] [<option>...] rad sync [--fetch | --announce] [<rid>] [<option>...]
rad sync [<rid>] [--fetch] [<rid>] [<option>...] rad sync --inventory [<option>...]
rad sync [<rid>] [--announce] [<rid>] [<option>...]
By default, the current repository is synchronized both ways. By default, the current repository is synchronized both ways.
If an <rid> is specified, that repository is synced instead. If an <rid> is specified, that repository is synced instead.
@ -39,10 +38,14 @@ Usage
When `--fetch` or `--announce` are specified on their own, this command When `--fetch` or `--announce` are specified on their own, this command
will only fetch or announce. will only fetch or announce.
If `--inventory` is specified, the node's inventory is announced to
the network. This mode does not take an `<rid>`.
Options Options
--fetch, -f Turn on fetching (default: true) --fetch, -f Turn on fetching (default: true)
--announce, -a Turn on announcing (default: true) --announce, -a Turn on ref announcing (default: true)
--inventory, -i Turn on inventory announcing (default: false)
--timeout <secs> How many seconds to wait while syncing --timeout <secs> How many seconds to wait while syncing
--seed <nid> Sync with the given node (may be specified multiple times) --seed <nid> Sync with the given node (may be specified multiple times)
--replicas, -r <count> Sync with a specific number of seeds --replicas, -r <count> Sync with a specific number of seeds
@ -51,25 +54,40 @@ Options
"#, "#,
}; };
#[derive(Debug, Default, PartialEq, Eq)]
pub struct SyncOptions {
mode: SyncMode,
direction: SyncDirection,
}
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum SyncMode { pub enum SyncMode {
Replicas(usize), Repo {
Seeds(Vec<NodeId>), mode: RepoSync,
direction: SyncDirection,
},
Inventory,
} }
impl Default for SyncMode { impl Default for SyncMode {
fn default() -> Self {
Self::Repo {
mode: RepoSync::default(),
direction: SyncDirection::default(),
}
}
}
/// Repository sync mode.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RepoSync {
/// Sync with N replicas.
Replicas(usize),
/// Sync with the given list of seeds.
Seeds(Vec<NodeId>),
}
impl Default for RepoSync {
fn default() -> Self { fn default() -> Self {
Self::Replicas(3) Self::Replicas(3)
} }
} }
#[derive(Debug, Default, PartialEq, Eq)] #[derive(Debug, Default, PartialEq, Eq, Clone)]
pub enum SyncDirection { pub enum SyncDirection {
Fetch, Fetch,
Announce, Announce,
@ -82,7 +100,7 @@ pub struct Options {
pub rid: Option<Id>, pub rid: Option<Id>,
pub verbose: bool, pub verbose: bool,
pub timeout: time::Duration, pub timeout: time::Duration,
pub sync: SyncOptions, pub sync: SyncMode,
} }
impl Args for Options { impl Args for Options {
@ -93,7 +111,11 @@ impl Args for Options {
let mut verbose = false; let mut verbose = false;
let mut timeout = time::Duration::from_secs(9); let mut timeout = time::Duration::from_secs(9);
let mut rid = None; let mut rid = None;
let mut sync = SyncOptions::default(); let mut fetch = false;
let mut announce = false;
let mut inventory = false;
let mut replicas = None;
let mut seeds = Vec::new();
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
@ -101,38 +123,28 @@ impl Args for Options {
verbose = true; verbose = true;
} }
Long("fetch") | Short('f') => { Long("fetch") | Short('f') => {
sync.direction = match sync.direction { fetch = true;
SyncDirection::Both => SyncDirection::Fetch,
SyncDirection::Announce => SyncDirection::Both,
SyncDirection::Fetch => SyncDirection::Fetch,
};
} }
Long("replicas") | Short('r') => { Long("replicas") | Short('r') => {
let val = parser.value()?; let val = parser.value()?;
let count = term::args::number(&val)?; let count = term::args::number(&val)?;
if let SyncMode::Replicas(ref mut r) = sync.mode { if count == 0 {
*r = count; anyhow::bail!("value for `--replicas` must be greater than zero");
} else {
anyhow::bail!("`--replicas` (-r) cannot be specified with `--seed`");
} }
replicas = Some(count);
} }
Long("seed") => { Long("seed") => {
let val = parser.value()?; let val = parser.value()?;
let nid = term::args::nid(&val)?; let nid = term::args::nid(&val)?;
if let SyncMode::Seeds(ref mut seeds) = sync.mode { seeds.push(nid);
seeds.push(nid);
} else {
sync.mode = SyncMode::Seeds(vec![nid]);
}
} }
Long("announce") | Short('a') => { Long("announce") | Short('a') => {
sync.direction = match sync.direction { announce = true;
SyncDirection::Both => SyncDirection::Announce, }
SyncDirection::Announce => SyncDirection::Announce, Long("inventory") | Short('i') => {
SyncDirection::Fetch => SyncDirection::Both, inventory = true;
};
} }
Long("timeout") | Short('t') => { Long("timeout") | Short('t') => {
let value = parser.value()?; let value = parser.value()?;
@ -152,11 +164,32 @@ impl Args for Options {
} }
} }
if sync.direction == SyncDirection::Announce { let sync = if inventory && (fetch || announce) {
if let SyncMode::Seeds(_) = sync.mode { anyhow::bail!("`--inventory` cannot be used with `--fetch` or `--announce`");
anyhow::bail!("`--seed` is only supported when fetching."); } else if inventory {
SyncMode::Inventory
} else {
let direction = match (fetch, announce) {
(true, true) | (false, false) => SyncDirection::Both,
(true, false) => SyncDirection::Fetch,
(false, true) => SyncDirection::Announce,
};
let mode = match (seeds, replicas) {
(seeds, Some(replicas)) => {
if seeds.is_empty() {
RepoSync::Replicas(replicas)
} else {
anyhow::bail!("`--replicas` cannot be specified with `--seed`");
}
}
(seeds, None) if !seeds.is_empty() => RepoSync::Seeds(seeds),
(_, None) => RepoSync::default(),
};
if direction == SyncDirection::Announce && matches!(mode, RepoSync::Seeds(_)) {
anyhow::bail!("`--seed` is only supported when fetching");
} }
} SyncMode::Repo { mode, direction }
};
Ok(( Ok((
Options { Options {
@ -187,31 +220,37 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
"to sync a repository, your node must be running. To start it, run `rad node start`" "to sync a repository, your node must be running. To start it, run `rad node start`"
); );
} }
let mode = options.sync.mode;
if [SyncDirection::Fetch, SyncDirection::Both].contains(&options.sync.direction) { match options.sync {
if !profile.tracking()?.is_repo_tracked(&rid)? { SyncMode::Repo { mode, direction } => {
anyhow::bail!("repository {rid} is not tracked"); if [SyncDirection::Fetch, SyncDirection::Both].contains(&direction) {
} if !profile.tracking()?.is_repo_tracked(&rid)? {
let results = fetch(rid, mode.clone(), options.timeout, &mut node)?; anyhow::bail!("repository {rid} is not tracked");
let success = results.success().count(); }
let failed = results.failed().count(); let results = fetch(rid, mode.clone(), options.timeout, &mut node)?;
let success = results.success().count();
let failed = results.failed().count();
if success == 0 { if success == 0 {
anyhow::bail!("repository fetch from {failed} seed(s) failed"); anyhow::bail!("repository fetch from {failed} seed(s) failed");
} else { } else {
term::success!("Fetched repository from {success} seed(s)"); term::success!("Fetched repository from {success} seed(s)");
}
}
if [SyncDirection::Announce, SyncDirection::Both].contains(&direction) {
announce_refs(rid, mode, options.timeout, node, &profile)?;
}
}
SyncMode::Inventory => {
announce_inventory(node)?;
} }
}
if [SyncDirection::Announce, SyncDirection::Both].contains(&options.sync.direction) {
announce(rid, mode, options.timeout, node, &profile)?;
} }
Ok(()) Ok(())
} }
fn announce( fn announce_refs(
rid: Id, rid: Id,
_mode: SyncMode, _mode: RepoSync,
timeout: time::Duration, timeout: time::Duration,
mut node: Node, mut node: Node,
profile: &Profile, profile: &Profile,
@ -257,14 +296,24 @@ fn announce(
Ok(()) Ok(())
} }
pub fn announce_inventory(mut node: Node) -> anyhow::Result<()> {
let peers = node.sessions()?.iter().filter(|s| s.is_connected()).count();
let spinner = term::spinner(format!("Announcing inventory to {peers} peers.."));
node.announce_inventory()?;
spinner.finish();
Ok(())
}
pub fn fetch( pub fn fetch(
rid: Id, rid: Id,
mode: SyncMode, mode: RepoSync,
timeout: time::Duration, timeout: time::Duration,
node: &mut Node, node: &mut Node,
) -> Result<FetchResults, node::Error> { ) -> Result<FetchResults, node::Error> {
match mode { match mode {
SyncMode::Seeds(seeds) => { RepoSync::Seeds(seeds) => {
let mut results = FetchResults::default(); let mut results = FetchResults::default();
for seed in seeds { for seed in seeds {
let result = fetch_from(rid, &seed, timeout, node)?; let result = fetch_from(rid, &seed, timeout, node)?;
@ -272,7 +321,7 @@ pub fn fetch(
} }
Ok(results) Ok(results)
} }
SyncMode::Replicas(count) => fetch_all(rid, count, timeout, node), RepoSync::Replicas(count) => fetch_all(rid, count, timeout, node),
} }
} }

View File

@ -131,7 +131,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
if options.fetch { if options.fetch {
sync::fetch( sync::fetch(
rid, rid,
sync::SyncMode::default(), sync::RepoSync::default(),
time::Duration::from_secs(6), time::Duration::from_secs(6),
&mut node, &mut node,
)?; )?;

View File

@ -406,6 +406,13 @@ pub struct Session {
pub state: State, pub state: State,
} }
impl Session {
/// Calls [`State::is_connected`] on the session state.
pub fn is_connected(&self) -> bool {
self.state.is_connected()
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Seed { pub struct Seed {