From 4a9dedfeab803bc2568938f0e7c50254d0c96868 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Thu, 6 Apr 2023 14:52:55 -0400 Subject: [PATCH 1/5] Added capability to copy Nodes without modification --- wnfs/src/private/directory.rs | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/wnfs/src/private/directory.rs b/wnfs/src/private/directory.rs index 1a879fd8..c264ab81 100644 --- a/wnfs/src/private/directory.rs +++ b/wnfs/src/private/directory.rs @@ -1123,6 +1123,33 @@ impl PrivateDirectory { Ok(()) } + /// Attaches a node to the specified directory without modifying the node. + #[allow(clippy::too_many_arguments)] + async fn attach_link( + self: &mut Rc, + node: PrivateNode, + path_segments: &[String], + search_latest: bool, + forest: &mut Rc, + store: &impl BlockStore, + ) -> Result<()> { + let (path, node_name) = crate::utils::split_last(path_segments)?; + let SearchResult::Found(dir) = self.get_leaf_dir_mut(path, search_latest, forest, store).await? else { + bail!(FsError::NotFound); + }; + + ensure!( + !dir.content.entries.contains_key(node_name), + FsError::FileAlreadyExists + ); + + dir.content + .entries + .insert(node_name.clone(), PrivateLink::from(node)); + + Ok(()) + } + /// Moves a file or directory from one path to another. /// /// # Examples @@ -1298,6 +1325,30 @@ impl PrivateDirectory { .await } + /// Copies a file or directory from one path to another without modifying it + #[allow(clippy::too_many_arguments)] + pub async fn cp_link( + self: &mut Rc, + path_segments_from: &[String], + path_segments_to: &[String], + search_latest: bool, + forest: &mut Rc, + store: &impl BlockStore, + ) -> Result<()> { + let result = self + .get_node(path_segments_from, search_latest, forest, store) + .await?; + + self.attach_link( + result.ok_or(FsError::NotFound)?, + path_segments_to, + search_latest, + forest, + store, + ) + .await + } + /// Stores this PrivateDirectory in the PrivateForest. /// /// # Examples From bdc2022d92f5847d8d32937130a9ec551bcf9956 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Tue, 18 Apr 2023 13:08:06 -0400 Subject: [PATCH 2/5] Added functions for inserting and retrieving PrivateFile based symlinks into a PrivateDirectory --- wnfs-common/src/metadata.rs | 9 +------ wnfs/src/private/directory.rs | 47 ++++++++++++++++++++++++++++++++- wnfs/src/private/file.rs | 43 ++++++++++++++++++++++++++++-- wnfs/src/private/node/header.rs | 2 +- 4 files changed, 89 insertions(+), 12 deletions(-) diff --git a/wnfs-common/src/metadata.rs b/wnfs-common/src/metadata.rs index 0080e2b5..49b44f6e 100644 --- a/wnfs-common/src/metadata.rs +++ b/wnfs-common/src/metadata.rs @@ -235,14 +235,7 @@ impl TryFrom<&str> for NodeType { impl From<&NodeType> for String { fn from(r#type: &NodeType) -> Self { - match r#type { - NodeType::PrivateDirectory => "wnfs/priv/dir".into(), - NodeType::PrivateFile => "wnfs/priv/file".into(), - NodeType::PublicDirectory => "wnfs/pub/dir".into(), - NodeType::PublicFile => "wnfs/pub/file".into(), - NodeType::TemporalSharePointer => "wnfs/share/temporal".into(), - NodeType::SnapshotSharePointer => "wnfs/share/snapshot".into(), - } + r#type.to_string() } } diff --git a/wnfs/src/private/directory.rs b/wnfs/src/private/directory.rs index c264ab81..77751447 100644 --- a/wnfs/src/private/directory.rs +++ b/wnfs/src/private/directory.rs @@ -6,7 +6,7 @@ use crate::{error::FsError, traits::Id, SearchResult}; use anyhow::{bail, ensure, Result}; use async_once_cell::OnceCell; use chrono::{DateTime, Utc}; -use libipld::Cid; +use libipld::{Cid, Ipld}; use rand_core::RngCore; use semver::Version; use serde::{de::Error as DeError, ser::Error as SerError, Deserialize, Deserializer, Serialize}; @@ -915,6 +915,51 @@ impl PrivateDirectory { Ok(()) } + /// Write a Symlink to the filesystem with the reference path at the path segments specified + pub async fn write_symlink( + self: &mut Rc, + path: String, + path_segments: &[String], + search_latest: bool, + time: DateTime, + forest: &PrivateForest, + store: &impl BlockStore, + rng: &mut impl RngCore, + ) -> Result<()> { + let (path_segments, filename) = crate::utils::split_last(path_segments)?; + + let dir = self + .get_or_create_leaf_dir_mut(path_segments, time, search_latest, forest, store, rng) + .await?; + + match dir + .lookup_node_mut(filename, search_latest, forest, store) + .await? + { + Some(PrivateNode::File(file)) => { + let file = file.prepare_next_revision()?; + file.content.content = super::FileContent::Inline { data: vec![] }; + file.content.metadata.upsert_mtime(time); + // Write the path into the Metadata HashMap + file.content.metadata.0.insert(String::from("symlink"), Ipld::String(path)); + } + Some(PrivateNode::Dir(_)) => bail!(FsError::DirectoryAlreadyExists), + None => { + let file = PrivateFile::new_symlink( + path, + dir.header.bare_name.clone(), + time, + rng, + ) + .await?; + let link = PrivateLink::with_file(file); + dir.content.entries.insert(filename.to_string(), link); + } + }; + + Ok(()) + } + /// Returns names and metadata of directory's immediate children. /// /// # Examples diff --git a/wnfs/src/private/file.rs b/wnfs/src/private/file.rs index 44fbecd3..41ebb291 100644 --- a/wnfs/src/private/file.rs +++ b/wnfs/src/private/file.rs @@ -7,8 +7,8 @@ use anyhow::Result; use async_once_cell::OnceCell; use async_stream::try_stream; use chrono::{DateTime, Utc}; -use futures::{future, AsyncRead, Stream, StreamExt, TryStreamExt}; -use libipld::{Cid, IpldCodec}; +use futures::{future, AsyncRead, Stream, StreamExt}; +use libipld::{Cid, IpldCodec, Ipld}; use rand_core::RngCore; use semver::Version; use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize}; @@ -380,6 +380,33 @@ impl PrivateFile { Ok(bytes) } + /// Create a new Symlink PrivateFile + pub async fn new_symlink( + path: String, + parent_bare_name: Namefilter, + time: DateTime, + rng: &mut impl RngCore, + ) -> Result { + // Header stays the same + let header = PrivateNodeHeader::new(parent_bare_name, rng); + // Symlinks have no file content + let content = FileContent::Inline { data: vec![] }; + // Create a new Metadata object + let mut metadata: Metadata = Metadata::new(time); + // Write the original path into the Metadata HashMap + metadata.0.insert(String::from("symlink"), Ipld::String(path)); + // Return self with PrivateFileContent + Ok(Self { + header, + content: PrivateFileContent { + persisted_as: OnceCell::new(), + metadata, + previous: BTreeSet::new(), + content, + }, + }) + } + /// Gets the metadata of the file pub fn get_metadata(&self) -> &Metadata { &self.content.metadata @@ -740,6 +767,18 @@ impl PrivateFile { pub fn as_node(self: &Rc) -> PrivateNode { PrivateNode::File(Rc::clone(self)) } + + /// If the Metadata contains Symlink data, return it + pub fn symlink_origin(&self) -> Option { + let meta = self.get_metadata(); + // If the Metadata contains a String key for the symlink + if let Some(Ipld::String(path)) = meta.0.get("symlink") { + Some(path.to_string()) + } + else { + None + } + } } impl PrivateFileContent { diff --git a/wnfs/src/private/node/header.rs b/wnfs/src/private/node/header.rs index 96ae2bf8..b0bf2c96 100644 --- a/wnfs/src/private/node/header.rs +++ b/wnfs/src/private/node/header.rs @@ -45,7 +45,7 @@ pub struct PrivateNodeHeader { /// A unique identifier of the node. pub(crate) inumber: INumber, /// Used both for versioning and deriving keys for that enforces privacy. - pub(crate) ratchet: Ratchet, + pub ratchet: Ratchet, /// Used for ancestry checks and as a key for the private forest. pub(crate) bare_name: Namefilter, } From 30fc25250bbb233358f036f3c9d5dde1cf6adfd8 Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Tue, 25 Apr 2023 10:41:49 -0400 Subject: [PATCH 3/5] fix: fmt and clippy --- wnfs/src/private/directory.rs | 15 +++++++-------- wnfs/src/private/file.rs | 9 +++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/wnfs/src/private/directory.rs b/wnfs/src/private/directory.rs index 77751447..d88ba37b 100644 --- a/wnfs/src/private/directory.rs +++ b/wnfs/src/private/directory.rs @@ -916,6 +916,7 @@ impl PrivateDirectory { } /// Write a Symlink to the filesystem with the reference path at the path segments specified + #[allow(clippy::too_many_arguments)] pub async fn write_symlink( self: &mut Rc, path: String, @@ -941,17 +942,15 @@ impl PrivateDirectory { file.content.content = super::FileContent::Inline { data: vec![] }; file.content.metadata.upsert_mtime(time); // Write the path into the Metadata HashMap - file.content.metadata.0.insert(String::from("symlink"), Ipld::String(path)); + file.content + .metadata + .0 + .insert(String::from("symlink"), Ipld::String(path)); } Some(PrivateNode::Dir(_)) => bail!(FsError::DirectoryAlreadyExists), None => { - let file = PrivateFile::new_symlink( - path, - dir.header.bare_name.clone(), - time, - rng, - ) - .await?; + let file = + PrivateFile::new_symlink(path, dir.header.bare_name.clone(), time, rng).await?; let link = PrivateLink::with_file(file); dir.content.entries.insert(filename.to_string(), link); } diff --git a/wnfs/src/private/file.rs b/wnfs/src/private/file.rs index 41ebb291..6c4abdab 100644 --- a/wnfs/src/private/file.rs +++ b/wnfs/src/private/file.rs @@ -8,7 +8,7 @@ use async_once_cell::OnceCell; use async_stream::try_stream; use chrono::{DateTime, Utc}; use futures::{future, AsyncRead, Stream, StreamExt}; -use libipld::{Cid, IpldCodec, Ipld}; +use libipld::{Cid, Ipld, IpldCodec}; use rand_core::RngCore; use semver::Version; use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize}; @@ -394,7 +394,9 @@ impl PrivateFile { // Create a new Metadata object let mut metadata: Metadata = Metadata::new(time); // Write the original path into the Metadata HashMap - metadata.0.insert(String::from("symlink"), Ipld::String(path)); + metadata + .0 + .insert(String::from("symlink"), Ipld::String(path)); // Return self with PrivateFileContent Ok(Self { header, @@ -774,8 +776,7 @@ impl PrivateFile { // If the Metadata contains a String key for the symlink if let Some(Ipld::String(path)) = meta.0.get("symlink") { Some(path.to_string()) - } - else { + } else { None } } From ab70ee7581ce7b65e0c9764a8a3e5a4a9766569e Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Fri, 5 May 2023 11:29:18 -0400 Subject: [PATCH 4/5] fix: compilation error for lack of import; re-privatized ratchet field --- wnfs/src/private/directory.rs | 2 +- wnfs/src/private/file.rs | 2 +- wnfs/src/private/node/header.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wnfs/src/private/directory.rs b/wnfs/src/private/directory.rs index d88ba37b..6bb4d7f4 100644 --- a/wnfs/src/private/directory.rs +++ b/wnfs/src/private/directory.rs @@ -1382,7 +1382,7 @@ impl PrivateDirectory { let result = self .get_node(path_segments_from, search_latest, forest, store) .await?; - + self.attach_link( result.ok_or(FsError::NotFound)?, path_segments_to, diff --git a/wnfs/src/private/file.rs b/wnfs/src/private/file.rs index 6c4abdab..85e9e834 100644 --- a/wnfs/src/private/file.rs +++ b/wnfs/src/private/file.rs @@ -7,7 +7,7 @@ use anyhow::Result; use async_once_cell::OnceCell; use async_stream::try_stream; use chrono::{DateTime, Utc}; -use futures::{future, AsyncRead, Stream, StreamExt}; +use futures::{future, AsyncRead, Stream, StreamExt, TryStreamExt}; use libipld::{Cid, Ipld, IpldCodec}; use rand_core::RngCore; use semver::Version; diff --git a/wnfs/src/private/node/header.rs b/wnfs/src/private/node/header.rs index b0bf2c96..96ae2bf8 100644 --- a/wnfs/src/private/node/header.rs +++ b/wnfs/src/private/node/header.rs @@ -45,7 +45,7 @@ pub struct PrivateNodeHeader { /// A unique identifier of the node. pub(crate) inumber: INumber, /// Used both for versioning and deriving keys for that enforces privacy. - pub ratchet: Ratchet, + pub(crate) ratchet: Ratchet, /// Used for ancestry checks and as a key for the private forest. pub(crate) bare_name: Namefilter, } From c9959bde076f13b9c9afd4459b06b03c9fabafcb Mon Sep 17 00:00:00 2001 From: Vera Gonzalez Date: Fri, 5 May 2023 13:14:12 -0400 Subject: [PATCH 5/5] fix: Added missing documentation for new functions fix: fmt --- wnfs/src/private/directory.rs | 102 ++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/wnfs/src/private/directory.rs b/wnfs/src/private/directory.rs index 6bb4d7f4..386e1a55 100644 --- a/wnfs/src/private/directory.rs +++ b/wnfs/src/private/directory.rs @@ -916,6 +916,50 @@ impl PrivateDirectory { } /// Write a Symlink to the filesystem with the reference path at the path segments specified + /// + /// # Examples + /// + /// ``` + /// use std::rc::Rc; + /// + /// use chrono::Utc; + /// use rand::thread_rng; + /// use wnfs::{ + /// private::{PrivateForest, PrivateRef, PrivateDirectory}, + /// common::{BlockStore, MemoryBlockStore}, + /// namefilter::Namefilter, + /// }; + /// + /// #[async_std::main] + /// async fn main() { + /// let store = &mut MemoryBlockStore::default(); + /// let rng = &mut thread_rng(); + /// let forest = &mut Rc::new(PrivateForest::new()); + /// let root_dir = &mut Rc::new(PrivateDirectory::new( + /// Namefilter::default(), + /// Utc::now(), + /// rng, + /// )); + /// let sym_path = "/pictures/meows".to_string(); + /// let path_segments = &["pictures".into(), "cats".into()]; + /// + /// root_dir + /// .write_symlink(sym_path.clone(), path_segments, true, Utc::now(), forest, store, rng) + /// .await + /// .unwrap(); + /// + /// let symlink = root_dir.get_node(path_segments, true, forest, store) + /// .await + /// .unwrap() + /// .expect("Symlink should be present") + /// .as_file() + /// .unwrap(); + /// + /// let path = symlink.symlink_origin(); + /// assert!(path.is_some()); + /// assert_eq!(path, Some(sym_path)); + /// } + /// ``` #[allow(clippy::too_many_arguments)] pub async fn write_symlink( self: &mut Rc, @@ -1370,6 +1414,64 @@ impl PrivateDirectory { } /// Copies a file or directory from one path to another without modifying it + /// + /// # Examples + /// + /// ``` + /// use std::rc::Rc; + /// + /// use chrono::Utc; + /// use rand::thread_rng; + /// + /// use wnfs::{ + /// private::{PrivateForest, PrivateRef, PrivateDirectory}, + /// common::{BlockStore, MemoryBlockStore}, + /// namefilter::Namefilter, + /// }; + /// + /// #[async_std::main] + /// async fn main() { + /// let store = &mut MemoryBlockStore::default(); + /// let rng = &mut thread_rng(); + /// let forest = &mut Rc::new(PrivateForest::new()); + /// let root_dir = &mut Rc::new(PrivateDirectory::new( + /// Namefilter::default(), + /// Utc::now(), + /// rng, + /// )); + /// + /// root_dir + /// .write( + /// &["code".into(), "python".into(), "hello.py".into()], + /// true, + /// Utc::now(), + /// b"print('hello world')".to_vec(), + /// forest, + /// store, + /// rng + /// ) + /// .await + /// .unwrap(); + /// + /// let result = root_dir + /// .cp_link( + /// &["code".into(), "python".into(), "hello.py".into()], + /// &["code".into(), "hello.py".into()], + /// true, + /// forest, + /// store + /// ) + /// .await + /// .unwrap(); + /// + /// let result = root_dir + /// .ls(&["code".into()], true, forest, store) + /// .await + /// .unwrap(); + /// + /// assert_eq!(result.len(), 2); + /// } + /// ``` #[allow(clippy::too_many_arguments)] pub async fn cp_link( self: &mut Rc,