Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement public directory cp & more efficient copy for PrivateFile #319

Merged
merged 3 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions wnfs-wasm/src/fs/public/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,30 @@ impl PublicDirectory {
}))
}

/// Copies a specific node to another location.
pub fn cp(
&self,
path_segments_from: &Array,
path_segments_to: &Array,
time: &Date,
store: BlockStore,
) -> JsResult<Promise> {
let mut directory = Rc::clone(&self.0);
let store = ForeignBlockStore(store);
let time = DateTime::<Utc>::from(time);
let path_segments_from = utils::convert_path_segments(path_segments_from)?;
let path_segments_to = utils::convert_path_segments(path_segments_to)?;

Ok(future_to_promise(async move {
(&mut directory)
.cp(&path_segments_from, &path_segments_to, time, &store)
.await
.map_err(error("Cannot copy content between directories"))?;

Ok(utils::create_public_op_result(directory, JsValue::NULL)?)
}))
}

/// Creates a new directory at the specified path.
///
/// This method acts like `mkdir -p` in Unix because it creates intermediate directories if they do not exist.
Expand Down
46 changes: 46 additions & 0 deletions wnfs-wasm/tests/public.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,52 @@ test.describe("PublicDirectory", () => {
expect(imagesContent[0].name).toEqual("cats");
});

test("cp can copy content between directories", async ({ page }) => {
const [imagesContent, picturesContent] = await page.evaluate(async () => {
const {
wnfs: { PublicDirectory },
mock: { MemoryBlockStore, sampleCID },
} = await window.setup();

const time = new Date();
const store = new MemoryBlockStore();
const root = new PublicDirectory(time);

var { rootDir } = await root.write(
["pictures", "cats", "luna.jpeg"],
sampleCID,
time,
store
);

var { rootDir } = await rootDir.write(
["pictures", "cats", "tabby.png"],
sampleCID,
time,
store
);

var { rootDir } = await rootDir.mkdir(["images"], time, store);

var { rootDir } = await rootDir.cp(
["pictures", "cats"],
["images", "cats"],
time,
store
);

const imagesContent = await rootDir.ls(["images"], store);

const picturesContent = await rootDir.ls(["pictures"], store);

return [imagesContent, picturesContent];
});

expect(imagesContent.length).toEqual(1);
expect(picturesContent.length).toEqual(1);
expect(imagesContent[0].name).toEqual("cats");
});

test("A PublicDirectory has the correct metadata", async ({ page }) => {
const result = await page.evaluate(async () => {
const {
Expand Down
10 changes: 4 additions & 6 deletions wnfs/src/private/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,11 +602,9 @@ impl PrivateDirectory {
/// .await?;
/// // Clone the forest that was used to write the file
/// // Open the file mutably
/// let file = {
/// root_dir
/// .open_file_mut(hello_py, true, Utc::now(), forest, store, rng)
/// .await?
/// };
/// let file = root_dir
/// .open_file_mut(hello_py, true, Utc::now(), forest, store, rng)
/// .await?;
/// // Define the content that will replace what is already in the file
/// let new_file_content = b"print('hello world 2')";
/// // Set the contents of the file, waiting for result and expecting no errors
Expand Down Expand Up @@ -1204,7 +1202,7 @@ impl PrivateDirectory {
/// .await
/// .unwrap();
///
/// let result = root_dir
/// root_dir
/// .cp(
/// &["code".into(), "python".into(), "hello.py".into()],
/// &["code".into(), "hello.py".into()],
Expand Down
58 changes: 50 additions & 8 deletions wnfs/src/private/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,56 @@ impl PrivateFile {
})
}

/// Create a copy of this file without re-encrypting the actual content
/// (if the ciphertext is external ciphertext), so this is really fast
/// even if the file contains gigabytes of data.
///
/// # Example
///
/// ```
/// use anyhow::Result;
/// use std::rc::Rc;
/// use chrono::Utc;
/// use rand::thread_rng;
/// use wnfs::{
/// private::{PrivateDirectory, PrivateFile, forest::{hamt::HamtForest, traits::PrivateForest}},
/// common::{MemoryBlockStore, utils::get_random_bytes},
/// };
///
/// #[async_std::main]
/// async fn main() -> Result<()> {
/// let store = &MemoryBlockStore::new();
/// let rng = &mut thread_rng();
/// let forest = &mut Rc::new(HamtForest::new_rsa_2048(rng));
///
/// let file = PrivateFile::with_content(
/// &forest.empty_name(),
/// Utc::now(),
/// get_random_bytes::<100>(rng).to_vec(),
/// forest,
/// store,
/// rng,
/// )
/// .await?;
///
/// let root_dir = &mut Rc::new(PrivateDirectory::new(&forest.empty_name(), Utc::now(), rng));
///
/// let copy = root_dir
/// .open_file_mut(&["some".into(), "copy.txt".into()], true, Utc::now(), forest, store, rng)
/// .await?;
///
/// copy.copy_content_from(&file, Utc::now());
///
/// assert_eq!(file.get_content(forest, store).await?, copy.get_content(forest, store).await?);
///
/// Ok(())
/// }
/// ```
pub fn copy_content_from(&mut self, other: &Self, time: DateTime<Utc>) {
self.content.metadata.upsert_mtime(time);
self.content.content = other.content.content.clone();
}

/// Streams the content of a file as chunk of blocks.
///
/// # Examples
Expand Down Expand Up @@ -663,24 +713,16 @@ impl PrivateFile {
/// will update the name to be the sub-name of given parent name,
/// so it inherits the write access rules from the new parent and
/// resets the `persisted_as` pointer.
/// Will copy and re-encrypt all external content.
pub(crate) async fn prepare_key_rotation(
&mut self,
parent_name: &Name,
forest: &mut impl PrivateForest,
store: &impl BlockStore,
rng: &mut impl CryptoRngCore,
) -> Result<()> {
let content = self.get_content(forest, store).await?;

self.header.inumber = NameSegment::new(rng);
self.header.update_name(parent_name);
self.header.reset_ratchet(rng);
self.content.persisted_as = OnceCell::new();

let content = Self::prepare_content(&self.header.name, content, forest, store, rng).await?;
self.content.content = content;

Ok(())
}

Expand Down
4 changes: 1 addition & 3 deletions wnfs/src/private/node/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,7 @@ impl PrivateNode {
match self {
Self::File(file_rc) => {
let file = Rc::make_mut(file_rc);

file.prepare_key_rotation(parent_name, forest, store, rng)
.await?;
file.prepare_key_rotation(parent_name, rng).await?;
}
Self::Dir(dir_rc) => {
let dir = Rc::make_mut(dir_rc);
Expand Down
75 changes: 75 additions & 0 deletions wnfs/src/public/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,81 @@ impl PublicDirectory {
Ok(())
}

/// Copies a file or directory from one path to another.
///
/// # Examples
///
/// ```
/// use anyhow::Result;
/// use std::rc::Rc;
/// use libipld_core::cid::Cid;
/// use chrono::Utc;
/// use rand::thread_rng;
/// use wnfs::{
/// public::PublicDirectory,
/// common::{BlockStore, MemoryBlockStore},
/// };
///
/// #[async_std::main]
/// async fn main() -> Result<()> {
/// let dir = &mut Rc::new(PublicDirectory::new(Utc::now()));
/// let store = &MemoryBlockStore::new();
///
/// dir
/// .write(
/// &["code".into(), "python".into(), "hello.py".into()],
/// Cid::default(),
/// Utc::now(),
/// store
/// )
/// .await?;
///
/// dir
/// .cp(
/// &["code".into(), "python".into(), "hello.py".into()],
/// &["code".into(), "hello.py".into()],
/// Utc::now(),
/// store
/// )
/// .await?;
///
/// let result = dir
/// .ls(&["code".into()], store)
/// .await?;
///
/// assert_eq!(result.len(), 2);
///
/// Ok(())
/// }
/// ```
pub async fn cp(
self: &mut Rc<Self>,
path_segments_from: &[String],
path_segments_to: &[String],
time: DateTime<Utc>,
store: &impl BlockStore,
) -> Result<()> {
let (path, filename) = utils::split_last(path_segments_to)?;
let Some(mut node) = self.get_node(path_segments_from, store).await?.cloned() else {
bail!(FsError::NotFound);
};

let SearchResult::Found(dir) = self.get_leaf_dir_mut(path, store).await? else {
bail!(FsError::NotFound);
};

ensure!(
!dir.userland.contains_key(filename),
FsError::FileAlreadyExists
);

node.upsert_mtime(time);

dir.userland.insert(filename.clone(), PublicLink::new(node));

Ok(())
}

#[async_recursion(?Send)]
/// Stores directory in provided block store.
///
Expand Down
Loading