diff --git a/iroh-api/src/api.rs b/iroh-api/src/api.rs index 20b11d5fb6..c066836d6a 100644 --- a/iroh-api/src/api.rs +++ b/iroh-api/src/api.rs @@ -46,12 +46,10 @@ pub trait Api { &self, ipfs_path: &IpfsPath, ) -> LocalBoxStream<'_, Result<(RelativePathBuf, OutType)>>; - fn add<'a>( - &'a self, - path: &'a Path, - recursive: bool, - no_wrap: bool, - ) -> LocalBoxFuture<'_, Result>; + + fn add_file<'a>(&'a self, path: &'a Path, wrap: bool) -> LocalBoxFuture<'_, Result>; + fn add_dir<'a>(&'a self, path: &'a Path, wrap: bool) -> LocalBoxFuture<'_, Result>; + fn check(&self) -> BoxFuture<'_, StatusTable>; fn watch(&self) -> LocalBoxFuture<'static, LocalBoxStream<'static, StatusTable>>; } @@ -125,23 +123,22 @@ impl Api for Iroh { .boxed_local() } - fn add<'a>( - &'a self, - path: &'a Path, - recursive: bool, - no_wrap: bool, - ) -> LocalBoxFuture<'_, Result> { + fn add_file<'a>(&'a self, path: &'a Path, wrap: bool) -> LocalBoxFuture<'_, Result> { async move { let providing_client = iroh_resolver::unixfs_builder::StoreAndProvideClient { client: Box::new(&self.client), }; - if path.is_dir() { - unixfs_builder::add_dir(Some(&providing_client), path, !no_wrap, recursive).await - } else if path.is_file() { - unixfs_builder::add_file(Some(&providing_client), path, !no_wrap).await - } else { - anyhow::bail!("can only add files or directories"); - } + unixfs_builder::add_file(Some(&providing_client), path, wrap).await + } + .boxed_local() + } + + fn add_dir<'a>(&'a self, path: &'a Path, wrap: bool) -> LocalBoxFuture<'_, Result> { + async move { + let providing_client = iroh_resolver::unixfs_builder::StoreAndProvideClient { + client: Box::new(&self.client), + }; + unixfs_builder::add_dir(Some(&providing_client), path, wrap).await } .boxed_local() } diff --git a/iroh-api/src/api_ext.rs b/iroh-api/src/api_ext.rs index f82b9cc349..f85d94c3fd 100644 --- a/iroh-api/src/api_ext.rs +++ b/iroh-api/src/api_ext.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use crate::{Api, IpfsPath, OutType}; +use crate::{Api, Cid, IpfsPath, OutType}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::Stream; @@ -29,6 +29,16 @@ pub trait ApiExt: Api { save_get_stream(&root_path, blocks).await?; Ok(root_path) } + + async fn add(&self, path: &Path, wrap: bool) -> Result { + if path.is_dir() { + self.add_dir(path, wrap).await + } else if path.is_file() { + self.add_file(path, wrap).await + } else { + anyhow::bail!("can only add files or directories") + } + } } impl ApiExt for T where T: Api {} diff --git a/iroh-resolver/src/unixfs_builder.rs b/iroh-resolver/src/unixfs_builder.rs index ca2be30633..95128f6c82 100644 --- a/iroh-resolver/src/unixfs_builder.rs +++ b/iroh-resolver/src/unixfs_builder.rs @@ -320,7 +320,6 @@ pub struct DirectoryBuilder { name: Option, entries: Vec, typ: DirectoryType, - recursive: bool, } impl Default for DirectoryBuilder { @@ -329,7 +328,6 @@ impl Default for DirectoryBuilder { name: None, entries: Default::default(), typ: DirectoryType::Basic, - recursive: false, } } } @@ -344,18 +342,12 @@ impl DirectoryBuilder { self } - pub fn recursive(&mut self) -> &mut Self { - self.recursive = true; - self - } - pub fn name>(&mut self, name: N) -> &mut Self { self.name = Some(name.into()); self } pub fn add_dir(&mut self, dir: Directory) -> Result<&mut Self> { - ensure!(self.recursive, "recursive directories not allowed"); Ok(self.entry(Entry::Directory(dir))) } @@ -471,15 +463,10 @@ pub async fn add_file(store: Option, path: &Path, wrap: bool) -> Re /// - storing the content using `rpc.store` /// - returns the root Cid /// - optionally wraps into a UnixFs directory to preserve the directory name -pub async fn add_dir( - store: Option, - path: &Path, - wrap: bool, - recursive: bool, -) -> Result { +pub async fn add_dir(store: Option, path: &Path, wrap: bool) -> Result { ensure!(path.is_dir(), "provided path was not a directory"); - let dir = make_dir_from_path(path, recursive).await?; + let dir = make_dir_from_path(path).await?; // encode and store let mut root = None; let parts = { @@ -504,7 +491,7 @@ pub async fn add_dir( } #[async_recursion(?Send)] -async fn make_dir_from_path>(path: P, recursive: bool) -> Result { +async fn make_dir_from_path>(path: P) -> Result { let path = path.into(); let mut dir = DirectoryBuilder::new(); dir.name( @@ -512,9 +499,6 @@ async fn make_dir_from_path>(path: P, recursive: bool) -> Resul .and_then(|s| s.to_str()) .unwrap_or_default(), ); - if recursive { - dir.recursive(); - } let mut directory_reader = tokio::fs::read_dir(path.clone()).await?; while let Some(entry) = directory_reader.next_entry().await? { let path = entry.path(); @@ -522,7 +506,7 @@ async fn make_dir_from_path>(path: P, recursive: bool) -> Resul let f = FileBuilder::new().path(path).build().await?; dir.add_file(f); } else if path.is_dir() { - let d = make_dir_from_path(path, recursive).await?; + let d = make_dir_from_path(path).await?; dir.add_dir(d)?; } else { anyhow::bail!("directory entry is neither file nor directory") @@ -597,16 +581,7 @@ mod tests { let dir = DirectoryBuilder::new(); let dir = dir.build()?; - let mut no_recursive = DirectoryBuilder::new(); - if no_recursive.add_dir(dir).is_ok() { - panic!("shouldn't be able to add a directory to a non-recursive directory builder"); - } - - let dir = DirectoryBuilder::new(); - let dir = dir.build()?; - let mut recursive_dir_builder = DirectoryBuilder::new(); - recursive_dir_builder.recursive(); recursive_dir_builder .add_dir(dir) .expect("recursive directories allowed"); @@ -703,7 +678,6 @@ mod tests { #[async_recursion(?Send)] async fn build_directory(name: &str, dir: &TestDir) -> Result { let mut builder = DirectoryBuilder::new(); - builder.recursive(); builder.name(name); for (name, entry) in dir { match entry { @@ -1027,7 +1001,7 @@ mod tests { entries: vec![Entry::File(file), Entry::Directory(nested_dir)], }; - let mut got = make_dir_from_path(dir, true).await?; + let mut got = make_dir_from_path(dir).await?; // Before comparison sort entries to make test deterministic. // The readdir_r function is used in the underlying platform which diff --git a/iroh/src/fixture.rs b/iroh/src/fixture.rs index 41c4a770f3..b942432def 100644 --- a/iroh/src/fixture.rs +++ b/iroh/src/fixture.rs @@ -1,8 +1,10 @@ use std::collections::HashMap; use std::env; +use std::future; +use std::str::FromStr; use futures::StreamExt; -use iroh_api::{Lookup, MockApi, MockP2p, OutType, PeerId}; +use iroh_api::{Cid, Lookup, MockApi, MockP2p, OutType, PeerId}; use relative_path::RelativePathBuf; type GetFixture = fn() -> MockApi; @@ -51,6 +53,26 @@ fn fixture_get() -> MockApi { api } +fn fixture_add_file() -> MockApi { + let mut api = MockApi::default(); + api.expect_add_file().returning(|_ipfs_path, _| { + Box::pin(future::ready( + Cid::from_str("QmYbcW4tXLXHWw753boCK8Y7uxLu5abXjyYizhLznq9PUR").map_err(|e| e.into()), + )) + }); + api +} + +fn fixture_add_directory() -> MockApi { + let mut api = MockApi::default(); + api.expect_add_dir().returning(|_ipfs_path, _| { + Box::pin(future::ready( + Cid::from_str("QmYbcW4tXLXHWw753boCK8Y7uxLu5abXjyYizhLznq9PUR").map_err(|e| e.into()), + )) + }); + api +} + fn fixture_get_wrapped_file() -> MockApi { let mut api = MockApi::default(); api.expect_get_stream().returning(|_ipfs_path| { @@ -90,6 +112,11 @@ fn register_fixtures() -> FixtureRegistry { "get_unwrapped_file".to_string(), fixture_get_unwrapped_file as GetFixture, ), + ("add_file".to_string(), fixture_add_file as GetFixture), + ( + "add_directory".to_string(), + fixture_add_directory as GetFixture, + ), ] .into_iter() .collect() diff --git a/iroh/src/run.rs b/iroh/src/run.rs index ecb2616d0f..0252372004 100644 --- a/iroh/src/run.rs +++ b/iroh/src/run.rs @@ -112,7 +112,13 @@ impl Cli { recursive, no_wrap, } => { - let cid = api.add(path, *recursive, *no_wrap).await?; + if path.is_dir() && !*recursive { + anyhow::bail!( + "{} is a directory, use --recursive to add it", + path.display() + ); + } + let cid = api.add(path, !(*no_wrap)).await?; println!("/ipfs/{}", cid); } Commands::Get { diff --git a/iroh/tests/cli_tests.rs b/iroh/tests/cli_tests.rs index 8ee84ba651..23896518f2 100644 --- a/iroh/tests/cli_tests.rs +++ b/iroh/tests/cli_tests.rs @@ -1,98 +1,128 @@ // using globs for trycmd unfortunately leads to some issues // when `.in` and `.out` directories are in use. So we avoid that -#[tokio::test] -async fn lookup_test() { +#[test] +fn add_directory_without_r_fails_test() { trycmd::TestCases::new() - .env("IROH_CTL_FIXTURE", "lookup") - .case("tests/cmd/lookup.trycmd") + .env("IROH_CTL_FIXTURE", "add_directory") + .case("tests/cmd/add_directory_without_r_fails.trycmd") .run(); } -#[tokio::test] -async fn get_success_cid_explicit_output_path_success_test() { +#[test] +fn add_directory_test() { trycmd::TestCases::new() - .env("IROH_CTL_FIXTURE", "get") - .case("tests/cmd/get_cid_explicit_output_path_success.trycmd") + .env("IROH_CTL_FIXTURE", "add_directory") + .case("tests/cmd/add_directory.trycmd") + .run(); +} + +#[test] +fn add_file_missing_test() { + trycmd::TestCases::new() + .env("IROH_CTL_FIXTURE", "add_file") + .case("tests/cmd/add_file_missing.trycmd") + .run(); +} + +#[test] +fn add_file_test() { + trycmd::TestCases::new() + .env("IROH_CTL_FIXTURE", "add_file") + .case("tests/cmd/add_file.trycmd") .run(); } -#[tokio::test] -async fn get_cid_success_test() { +#[test] +fn get_cid_directory_overwrite_explicit_failure_test() { trycmd::TestCases::new() .env("IROH_CTL_FIXTURE", "get") - .case("tests/cmd/get_cid_success.trycmd") + .case("tests/cmd/get_cid_directory_overwrite_explicit_failure.trycmd") .run(); } -#[tokio::test] -async fn get_ipfs_path_success_test() { +#[test] +fn get_cid_directory_overwrite_failure_test() { trycmd::TestCases::new() .env("IROH_CTL_FIXTURE", "get") - .case("tests/cmd/get_ipfs_path_success.trycmd") + .case("tests/cmd/get_cid_directory_overwrite_failure.trycmd") .run(); } -#[tokio::test] -async fn get_tail_success_test() { - // we use the get_unwrapped_file fixture because it delivers a file - // which is what the test simulates +#[test] +fn get_cid_explicit_output_path_success_test() { trycmd::TestCases::new() - .env("IROH_CTL_FIXTURE", "get_unwrapped_file") - .case("tests/cmd/get_tail_success.trycmd") + .env("IROH_CTL_FIXTURE", "get") + .case("tests/cmd/get_cid_explicit_output_path_success.trycmd") .run(); } -#[tokio::test] -async fn get_cid_directory_overwrite_expicit_failure_test() { +#[test] +fn get_cid_success_test() { trycmd::TestCases::new() .env("IROH_CTL_FIXTURE", "get") - .case("tests/cmd/get_cid_directory_overwrite_explicit_failure.trycmd") + .case("tests/cmd/get_cid_success.trycmd") .run(); } -#[tokio::test] -async fn get_cid_directory_overwrite_failure_test() { +#[test] +fn get_failure_test() { trycmd::TestCases::new() .env("IROH_CTL_FIXTURE", "get") - .case("tests/cmd/get_cid_directory_overwrite_failure.trycmd") + .case("tests/cmd/get_failure.trycmd") .run(); } -#[tokio::test] -async fn get_wrapped_file_test() { +#[test] +fn get_ipfs_path_success_test() { trycmd::TestCases::new() - .env("IROH_CTL_FIXTURE", "get_wrapped_file") - .case("tests/cmd/get_wrapped_file.trycmd") + .env("IROH_CTL_FIXTURE", "get") + .case("tests/cmd/get_ipfs_path_success.trycmd") .run(); } -#[tokio::test] -async fn get_unwrapped_file_test() { +#[test] +fn get_tail_success_test() { trycmd::TestCases::new() .env("IROH_CTL_FIXTURE", "get_unwrapped_file") - .case("tests/cmd/get_unwrapped_file.trycmd") + .case("tests/cmd/get_tail_success.trycmd") .run(); } -#[tokio::test] -async fn get_unwrapped_file_overwrite_test() { +#[test] +fn get_unwrapped_file_overwrite_test() { trycmd::TestCases::new() .env("IROH_CTL_FIXTURE", "get_unwrapped_file") .case("tests/cmd/get_unwrapped_file_overwrite.trycmd") .run(); } -#[tokio::test] -async fn get_failure_test() { +#[test] +fn get_unwrapped_file_test() { trycmd::TestCases::new() - .env("IROH_CTL_FIXTURE", "get") - .case("tests/cmd/get_failure.trycmd") + .env("IROH_CTL_FIXTURE", "get_unwrapped_file") + .case("tests/cmd/get_unwrapped_file.trycmd") + .run(); +} + +#[test] +fn get_wrapped_file_test() { + trycmd::TestCases::new() + .env("IROH_CTL_FIXTURE", "get_wrapped_file") + .case("tests/cmd/get_wrapped_file.trycmd") + .run(); +} + +#[test] +fn lookup_test() { + trycmd::TestCases::new() + .env("IROH_CTL_FIXTURE", "lookup") + .case("tests/cmd/lookup.trycmd") .run(); } -#[tokio::test] -async fn version_cli_test() { +#[test] +fn version_test() { trycmd::TestCases::new() .case("tests/cmd/version.trycmd") .run(); diff --git a/iroh/tests/cmd/add_directory.in/mydir/file.txt b/iroh/tests/cmd/add_directory.in/mydir/file.txt new file mode 100644 index 0000000000..5ab2f8a432 --- /dev/null +++ b/iroh/tests/cmd/add_directory.in/mydir/file.txt @@ -0,0 +1 @@ +Hello \ No newline at end of file diff --git a/iroh/tests/cmd/add_directory.trycmd b/iroh/tests/cmd/add_directory.trycmd new file mode 100644 index 0000000000..a795a78f16 --- /dev/null +++ b/iroh/tests/cmd/add_directory.trycmd @@ -0,0 +1,5 @@ +``` +$ iroh add -r mydir +/ipfs/QmYbcW4tXLXHWw753boCK8Y7uxLu5abXjyYizhLznq9PUR + +``` \ No newline at end of file diff --git a/iroh/tests/cmd/add_directory_without_r_fails.in/mydir/file.txt b/iroh/tests/cmd/add_directory_without_r_fails.in/mydir/file.txt new file mode 100644 index 0000000000..5ab2f8a432 --- /dev/null +++ b/iroh/tests/cmd/add_directory_without_r_fails.in/mydir/file.txt @@ -0,0 +1 @@ +Hello \ No newline at end of file diff --git a/iroh/tests/cmd/add_directory_without_r_fails.trycmd b/iroh/tests/cmd/add_directory_without_r_fails.trycmd new file mode 100644 index 0000000000..8d8e9a656b --- /dev/null +++ b/iroh/tests/cmd/add_directory_without_r_fails.trycmd @@ -0,0 +1,6 @@ +``` +$ iroh add mydir +? failed +Error: mydir is a directory, use --recursive to add it + +``` \ No newline at end of file diff --git a/iroh/tests/cmd/add_file.in/file.txt b/iroh/tests/cmd/add_file.in/file.txt new file mode 100644 index 0000000000..7cdabb25a1 --- /dev/null +++ b/iroh/tests/cmd/add_file.in/file.txt @@ -0,0 +1 @@ +A file with contents \ No newline at end of file diff --git a/iroh/tests/cmd/add_file.trycmd b/iroh/tests/cmd/add_file.trycmd new file mode 100644 index 0000000000..2bd8e25f07 --- /dev/null +++ b/iroh/tests/cmd/add_file.trycmd @@ -0,0 +1,5 @@ +``` +$ iroh add file.txt +/ipfs/QmYbcW4tXLXHWw753boCK8Y7uxLu5abXjyYizhLznq9PUR + +``` \ No newline at end of file diff --git a/iroh/tests/cmd/add_file_missing.trycmd b/iroh/tests/cmd/add_file_missing.trycmd new file mode 100644 index 0000000000..ef0f7244e2 --- /dev/null +++ b/iroh/tests/cmd/add_file_missing.trycmd @@ -0,0 +1,6 @@ +``` +$ iroh add missing.txt +? failed +Error: can only add files or directories + +``` \ No newline at end of file