diff --git a/iroh/src/commands/doc.rs b/iroh/src/commands/doc.rs index 57e6261d829..9e5afb6eea4 100644 --- a/iroh/src/commands/doc.rs +++ b/iroh/src/commands/doc.rs @@ -20,7 +20,7 @@ use iroh::{ client::{Doc, Iroh}, rpc_protocol::{DocTicket, ProviderService, SetTagOption, ShareMode, WrapOption}, sync_engine::{LiveEvent, Origin}, - util::fs::{path_content_info, PathContent}, + util::fs::{path_content_info, path_to_key, PathContent}, }; use iroh_bytes::{provider::AddProgress, Hash, Tag}; use iroh_sync::{ @@ -182,6 +182,9 @@ pub enum DocCommands { /// Moving a file imported with `in-place` will result in data corruption #[clap(short, long)] in_place: bool, + /// When true, you will not get a prompt to confirm you want to import the files + #[clap(long, default_value_t = false)] + no_prompt: bool, }, /// Export the most recent data for a key from a document Export { @@ -392,6 +395,7 @@ impl DocCommands { prefix, path, in_place, + no_prompt, } => { let doc = get_doc(iroh, env, doc).await?; let author = env.author(author)?; @@ -409,16 +413,18 @@ impl DocCommands { // and confirm with the user that they still want to import the file let PathContent { size, files } = tokio::task::spawn_blocking(|| path_content_info(root0)).await??; - let prompt = format!("Import {files} files totaling {}?", HumanBytes(size)); - if !Confirm::new() - .with_prompt(prompt) - .interact() - .unwrap_or(false) - { - println!("Aborted."); - return Ok(()); - } else { - print!("\r"); + if !no_prompt { + let prompt = format!("Import {files} files totaling {}?", HumanBytes(size)); + if !Confirm::new() + .with_prompt(prompt) + .interact() + .unwrap_or(false) + { + println!("Aborted."); + return Ok(()); + } else { + print!("\r"); + } } let stream = iroh @@ -725,20 +731,21 @@ where Some((path_str, size, ref mut h, last_val)) => { imp.add_progress(*size - *last_val); imp.import_found(path_str.clone()); + let path = PathBuf::from(path_str.clone()); *h = Some(hash); - let key = match key_from_path_str( - root.clone(), - prefix.clone(), - path_str.clone(), - ) { - Ok(k) => k, - Err(e) => { - tracing::info!("error getting key from {}, id {id}", path_str); - return Some(Err(anyhow::anyhow!( - "Issue creating a key for entry {hash:?}: {e}" - ))); - } - }; + let key = + match path_to_key(path, Some(prefix.clone()), Some(root.clone())) { + Ok(k) => k.to_vec(), + Err(e) => { + tracing::info!( + "error getting key from {}, id {id}", + path_str + ); + return Some(Err(anyhow::anyhow!( + "Issue creating a key for entry {hash:?}: {e}" + ))); + } + }; // send update to doc tracing::info!( "setting entry {} (id: {id}) to doc", @@ -788,20 +795,6 @@ where Ok(()) } -/// Creates a document key from the path, removing the full canonicalized path, and adding -/// whatever prefix the user requests. -fn key_from_path_str(root: PathBuf, prefix: String, path_str: String) -> Result> { - let suffix = PathBuf::from(path_str) - .strip_prefix(root)? - .to_str() - .map(|p| p.as_bytes()) - .ok_or(anyhow!("could not convert path to bytes"))? - .to_vec(); - let mut key = prefix.into_bytes().to_vec(); - key.extend(suffix); - Ok(key) -} - #[derive(Debug, Clone)] struct ImportProgressBar { mp: MultiProgress, @@ -854,3 +847,75 @@ impl ImportProgressBar { self.mp.clear().ok(); } } + +#[cfg(test)] +mod tests { + use crate::commands::start::StartArgs; + + use super::*; + + use iroh_bytes::util::runtime; + + #[tokio::test] + async fn test_doc_import() -> Result<()> { + let temp_dir = tempfile::tempdir().context("tempdir")?; + + tokio::fs::create_dir_all(temp_dir.path()) + .await + .context("create dir all")?; + + let foobar = temp_dir.path().join("foobar"); + tokio::fs::write(foobar, "foobar") + .await + .context("write foobar")?; + let foo = temp_dir.path().join("foo"); + tokio::fs::write(foo, "foo").await.context("write foo")?; + + let data_dir = tempfile::tempdir()?; + + // run iroh node in the background, as if running `iroh start` + std::env::set_var("IROH_DATA_DIR", data_dir.path().as_os_str()); + let rt = runtime::Handle::from_current(1).context("runtime Handle")?; + let start_args = StartArgs { + addr: std::net::SocketAddr::from(iroh::node::DEFAULT_BIND_ADDR), + request_token: None, + rpc_port: 0, + }; + + let node = start_args + .start_node(&rt, None, None) + .await + .context("run start command")?; + let client = node.client(); + let doc = client.docs.create().await.context("doc create")?; + let author = client.authors.create().await.context("authors create")?; + + // set up command, getting iroh node + let cli = ConsoleEnv::for_console().context("ConsoleEnv")?; + let iroh = crate::commands::iroh_quic_connect(rt) + .await + .context("rpc connect")?; + let command = DocCommands::Import { + doc: Some(doc.id()), + author: Some(author), + prefix: None, + path: temp_dir.path().to_string_lossy().into(), + in_place: false, + no_prompt: true, + }; + + command.run(&iroh, &cli).await.context("DocCommands run")?; + + let keys: Vec<_> = doc + .get_many(Query::all()) + .await + .context("doc get many")? + .try_collect() + .await?; + assert_eq!(2, keys.len()); + + node.shutdown(); + node.await?; + Ok(()) + } +} diff --git a/iroh/src/commands/start.rs b/iroh/src/commands/start.rs index 44654998c0d..e21eda26f5a 100644 --- a/iroh/src/commands/start.rs +++ b/iroh/src/commands/start.rs @@ -47,20 +47,20 @@ pub struct StartArgs { /// /// Only used with `start` or `--start` #[clap(long, short, global = true, default_value_t = SocketAddr::from(iroh::node::DEFAULT_BIND_ADDR))] - addr: SocketAddr, + pub addr: SocketAddr, /// Use a token to authenticate requests for data. /// /// Pass "random" to generate a random token, or base32-encoded bytes to use as a token /// /// Only used with `start` or `--start` #[clap(long, global = true)] - request_token: Option, + pub request_token: Option, /// The RPC port that the the Iroh node will listen on. /// /// Only used with `start` or `--start` #[clap(long, global = true, default_value_t = DEFAULT_RPC_PORT)] - rpc_port: u16, + pub rpc_port: u16, } impl StartArgs { @@ -162,7 +162,7 @@ impl StartArgs { Ok(()) } - async fn start_node( + pub(crate) async fn start_node( &self, rt: &runtime::Handle, token: Option,