diff --git a/monofs/examples/dir_ops.rs b/monofs/examples/dir_ops.rs index c326965..ea0373a 100644 --- a/monofs/examples/dir_ops.rs +++ b/monofs/examples/dir_ops.rs @@ -59,8 +59,8 @@ async fn main() -> FsResult<()> { println!("Copied file: {:?}", copied_file); // Remove a file - let (removed_name, removed_entity) = root.remove("docs/readme.md").await?; - println!("Removed '{}': {:?}", removed_name, removed_entity); + root.remove("docs/readme.md").await?; + println!("Removed 'docs/readme.md'"); // Create and add a subdirectory root.put_dir("subdir", Dir::new(store.clone()))?; @@ -77,7 +77,7 @@ async fn main() -> FsResult<()> { } // Check if an entry exists - let file_exists = root.has_entry("example.txt").await?; + let file_exists = root.has_entry("example.txt")?; println!("'example.txt' exists: {}", file_exists); // Get and modify a subdirectory diff --git a/monofs/lib/filesystem/dir.rs b/monofs/lib/filesystem/dir.rs index 80750e3..2c429ca 100644 --- a/monofs/lib/filesystem/dir.rs +++ b/monofs/lib/filesystem/dir.rs @@ -139,12 +139,12 @@ where /// /// dir.put_entry(file_name, file_cid.into())?; /// - /// assert!(dir.has_entry(file_name).await?); - /// assert!(!dir.has_entry("nonexistent.txt").await?); + /// assert!(dir.has_entry(file_name)?); + /// assert!(!dir.has_entry("nonexistent.txt")?); /// # Ok(()) /// # } /// ``` - pub async fn has_entry(&self, name: impl AsRef) -> FsResult { + pub fn has_entry(&self, name: impl AsRef) -> FsResult { let name = Utf8UnixPathSegment::from_str(name.as_ref())?; Ok(self.inner.entries.contains_key(&name)) } @@ -335,7 +335,14 @@ where S: Send + Sync, { match self.get_entry(name)? { - Some(link) => Ok(Some(link.resolve_entity(self.inner.store.clone()).await?)), + Some(link) => { + let entity = link.resolve_entity(self.inner.store.clone()).await?; + if entity.get_metadata().get_deleted_at().is_some() { + Ok(None) + } else { + Ok(Some(entity)) + } + } None => Ok(None), } } @@ -350,7 +357,14 @@ where { let store = self.inner.store.clone(); match self.get_entry_mut(name)? { - Some(link) => Ok(Some(link.resolve_entity_mut(store).await?)), + Some(link) => { + let entity = link.resolve_entity_mut(store).await?; + if entity.get_metadata().get_deleted_at().is_some() { + Ok(None) + } else { + Ok(Some(entity)) + } + } None => Ok(None), } } @@ -655,8 +669,8 @@ mod tests { loaded_dir_metadata.get_sync_type() ); assert_eq!( - dir_metadata.get_tombstone(), - loaded_dir_metadata.get_tombstone() + dir_metadata.get_deleted_at(), + loaded_dir_metadata.get_deleted_at() ); assert_eq!( dir_metadata.get_created_at(), @@ -689,8 +703,8 @@ mod tests { dir.put_entry(file_name, file_cid.into())?; - assert!(dir.has_entry(file_name).await?); - assert!(!dir.has_entry("nonexistent.txt").await?); + assert!(dir.has_entry(file_name)?); + assert!(!dir.has_entry("nonexistent.txt")?); Ok(()) } @@ -703,11 +717,11 @@ mod tests { "bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq".parse()?; dir.put_entry(file_name, file_cid.clone().into())?; - assert!(dir.has_entry(file_name).await?); + assert!(dir.has_entry(file_name)?); let removed_entry = dir.remove_entry(file_name)?; assert_eq!(removed_entry.get_cid(), Some(&file_cid)); - assert!(!dir.has_entry(file_name).await?); + assert!(!dir.has_entry(file_name)?); assert!(dir.remove_entry("nonexistent.txt").is_err()); @@ -722,7 +736,7 @@ mod tests { assert_eq!(*metadata.get_entity_type(), EntityType::Dir); assert_eq!(*metadata.get_symlink_depth(), DEFAULT_SYMLINK_DEPTH); assert_eq!(*metadata.get_sync_type(), SyncType::RAFT); - assert!(!metadata.get_tombstone()); + assert!(metadata.get_deleted_at().is_none()); Ok(()) } diff --git a/monofs/lib/filesystem/dir/find.rs b/monofs/lib/filesystem/dir/find.rs index 5d49c24..cab094e 100644 --- a/monofs/lib/filesystem/dir/find.rs +++ b/monofs/lib/filesystem/dir/find.rs @@ -52,31 +52,7 @@ pub type FindResultDirMut<'a, S> = FindResult<&'a mut Dir>; /// following the path specified by `path`. It attempts to resolve each component of the path /// until it either finds the target directory, encounters an error, or determines that the path /// is not found or invalid. -/// -/// ## Examples -/// -/// ``` -/// use monofs::filesystem::{Dir, find_dir}; -/// use monoutils_store::MemoryStore; -/// -/// # #[tokio::main] -/// # async fn main() -> anyhow::Result<()> { -/// let store = MemoryStore::default(); -/// let root_dir = Dir::new(store); -/// let result = find_dir(&root_dir, "some/path/to/entity").await?; -/// # Ok(()) -/// # } -/// ``` -/// -/// ## Note -/// -/// The function does not support the following path components: -/// - `.` -/// - `..` -/// - `/` -/// -/// If any of these components are present in the path, the function will return an error. -pub async fn find_dir(mut dir: &Dir, path: impl AsRef) -> FsResult> +pub(crate) async fn find_dir(mut dir: &Dir, path: impl AsRef) -> FsResult> where S: IpldStore + Send + Sync, { @@ -125,31 +101,7 @@ where /// following the path specified by `path`. It attempts to resolve each component of the path /// until it either finds the target directory, encounters an error, or determines that the path /// is not found or invalid. -/// -/// ## Examples -/// -/// ``` -/// use monofs::filesystem::{Dir, find_dir_mut}; -/// use monoutils_store::MemoryStore; -/// -/// # #[tokio::main] -/// # async fn main() -> anyhow::Result<()> { -/// let store = MemoryStore::default(); -/// let mut root_dir = Dir::new(store); -/// let result = find_dir_mut(&mut root_dir, "some/path/to/entity").await?; -/// # Ok(()) -/// # } -/// ``` -/// -/// ## Note -/// -/// The function does not support the following path components: -/// - `.` -/// - `..` -/// - `/` -/// -/// If any of these components are present in the path, the function will return an error. -pub async fn find_dir_mut( +pub(crate) async fn find_dir_mut( mut dir: &mut Dir, path: impl AsRef, ) -> FsResult> @@ -201,23 +153,7 @@ where /// This function checks the existence of an entity at the given path. If the entity /// exists, it returns the entity. If the entity does not exist, it creates a new /// directory hierarchy and returns the new entity. -/// -/// ## Examples -/// -/// ``` -/// use monofs::filesystem::{Dir, find_or_create_dir}; -/// use monoutils_store::MemoryStore; -/// -/// # #[tokio::main] -/// # async fn main() -> anyhow::Result<()> { -/// let store = MemoryStore::default(); -/// let mut root_dir = Dir::new(store); -/// let new_dir = find_or_create_dir(&mut root_dir, "new/nested/directory").await?; -/// assert!(new_dir.is_empty()); -/// # Ok(()) -/// # } -/// ``` -pub async fn find_or_create_dir(dir: &mut Dir, path: impl AsRef) -> FsResult<&mut Dir> +pub(crate) async fn find_or_create_dir(dir: &mut Dir, path: impl AsRef) -> FsResult<&mut Dir> where S: IpldStore + Send + Sync, { diff --git a/monofs/lib/filesystem/dir/ops.rs b/monofs/lib/filesystem/dir/ops.rs index 661ca16..3bdf15f 100644 --- a/monofs/lib/filesystem/dir/ops.rs +++ b/monofs/lib/filesystem/dir/ops.rs @@ -1,8 +1,9 @@ +use chrono::Utc; use monoutils_store::IpldStore; use typed_path::Utf8UnixPath; use crate::{ - filesystem::{dir::find, entity::Entity, file::File, EntityCidLink, FsError, FsResult}, + filesystem::{dir::find, entity::Entity, file::File, FsError, FsResult}, utils::path, }; @@ -20,7 +21,7 @@ where /// Finds an entity in the directory structure given a path. /// /// This method traverses the directory structure to find the entity specified by the path. - /// It returns a reference to the found entity if it exists. + /// It returns a reference to the found entity if it exists and is not marked as deleted. /// /// ## Examples /// @@ -35,6 +36,10 @@ where /// /// let entity = dir.find("foo/bar.txt").await?; /// assert!(matches!(entity, Some(Entity::File(_)))); + /// + /// // After removing, find returns None + /// dir.remove("foo/bar.txt").await?; + /// assert!(dir.find("foo/bar.txt").await?.is_none()); /// # Ok(()) /// # } /// ``` @@ -59,6 +64,7 @@ where /// Finds an entity in the directory structure given a path, returning a mutable reference. /// /// This method is similar to `find`, but it returns a mutable reference to the found entity. + /// It will skip entities that are marked as deleted. /// /// ## Examples /// @@ -73,6 +79,10 @@ where /// /// let entity = dir.find_mut("foo/bar.txt").await?; /// assert!(matches!(entity, Some(Entity::File(_)))); + /// + /// // After removing, find_mut returns None + /// dir.remove("foo/bar.txt").await?; + /// assert!(dir.find_mut("foo/bar.txt").await?.is_none()); /// # Ok(()) /// # } /// ``` @@ -136,7 +146,7 @@ where None => self, }; - if parent_dir.has_entry(&file_name).await? { + if parent_dir.has_entry(&file_name)? { return parent_dir .get_entity_mut(&file_name) .await? @@ -256,6 +266,9 @@ where /// Removes an entity at the specified path and returns it. /// + /// This method completely removes the entity from its parent directory, leaving no trace. + /// For a version that marks the entity as deleted but keeps it in the structure, use `remove`. + /// /// ## Examples /// /// ``` @@ -267,19 +280,12 @@ where /// let mut dir = Dir::new(MemoryStore::default()); /// dir.find_or_create("foo/bar.txt", true).await?; /// - /// let (filename, _entity) = dir.remove("foo/bar.txt").await?; - /// - /// assert_eq!(filename, "bar.txt".parse()?); + /// dir.remove_trace("foo/bar.txt").await?; /// assert!(dir.find("foo/bar.txt").await?.is_none()); /// # Ok(()) /// # } /// ``` - /// - /// TODO: Add support for tombstone files. - pub async fn remove( - &mut self, - path: impl AsRef, - ) -> FsResult<(Utf8UnixPathSegment, EntityCidLink)> { + pub async fn remove_trace(&mut self, path: impl AsRef) -> FsResult<()> { let path = Utf8UnixPath::new(path.as_ref()); if path.has_root() { @@ -303,9 +309,46 @@ where self }; - let entity = parent_dir.remove_entry(&filename)?; + parent_dir.remove_entry(&filename)?; + Ok(()) + } + + /// Removes an entity at the specified path by marking it as deleted. + /// + /// This method marks the entity as deleted by setting its deleted_at timestamp, + /// but keeps it in the directory structure. The entity will be skipped by find operations. + /// + /// ## Examples + /// + /// ``` + /// use monofs::filesystem::{Dir, Entity, FsResult}; + /// use monoutils_store::MemoryStore; + /// + /// # #[tokio::main] + /// # async fn main() -> FsResult<()> { + /// let mut dir = Dir::new(MemoryStore::default()); + /// dir.find_or_create("foo/bar.txt", true).await?; + /// + /// dir.remove("foo/bar.txt").await?; + /// + /// // The entity still exists but is marked as deleted + /// assert!(dir.find("foo/bar.txt").await?.is_none()); + /// # Ok(()) + /// # } + /// ``` + pub async fn remove(&mut self, path: impl AsRef) -> FsResult<()> { + let path = Utf8UnixPath::new(path.as_ref()); + + if path.has_root() { + return Err(FsError::PathHasRoot(path.to_string())); + } - Ok((filename, entity)) + if let Some(entity) = self.find_mut(path).await? { + entity.get_metadata_mut().set_deleted_at(Some(Utc::now())); + Ok(()) + } else { + Err(FsError::PathNotFound(path.to_string())) + } } /// Renames (moves) an entity from one path to another. @@ -351,11 +394,11 @@ where // First check if target exists to fail fast let target_exists = if let Some(parent_path) = new_parent { match find::find_dir(self, parent_path).await? { - find::FindResult::Found { dir } => dir.has_entry(&new_filename).await?, + find::FindResult::Found { dir } => dir.has_entry(&new_filename)?, _ => return Err(FsError::PathNotFound(new_path.to_string())), } } else { - self.has_entry(&new_filename).await? + self.has_entry(&new_filename)? }; if target_exists { @@ -409,9 +452,32 @@ where #[cfg(test)] mod tests { - use super::*; use monoutils_store::{MemoryStore, Storable}; + use super::*; + + mod fixtures { + use super::*; + + pub(super) async fn setup_test_filesystem() -> FsResult> { + let store = MemoryStore::default(); + let mut root = Dir::new(store.clone()); + + // Create a complex nested structure + root.find_or_create("projects/web/index.html", true).await?; + root.find_or_create("projects/web/styles/main.css", true) + .await?; + root.find_or_create("projects/app/src/main.rs", true) + .await?; + root.find_or_create("documents/personal/notes.txt", true) + .await?; + root.find_or_create("documents/work/report.pdf", true) + .await?; + + Ok(root) + } + } + #[tokio::test] async fn test_ops_find() -> FsResult<()> { let mut dir = Dir::new(MemoryStore::default()); @@ -562,7 +628,7 @@ mod tests { } #[tokio::test] - async fn test_ops_remove() -> FsResult<()> { + async fn test_ops_remove_trace() -> FsResult<()> { let mut dir = Dir::new(MemoryStore::default()); // Create entities to remove @@ -570,16 +636,12 @@ mod tests { dir.find_or_create("baz", false).await?; // Remove file - let (filename, entity) = dir.remove("foo/bar.txt").await?; - assert_eq!(filename, "bar.txt".parse()?); - assert!(matches!(entity, EntityCidLink::Decoded(Entity::File(_)))); + dir.remove_trace("foo/bar.txt").await?; assert!(dir.find("foo/bar.txt").await?.is_none()); assert!(dir.find("foo").await?.is_some()); // Remove directory - let (dirname, entity) = dir.remove("baz").await?; - assert_eq!(dirname, "baz".parse()?); - assert!(matches!(entity, EntityCidLink::Decoded(Entity::Dir(_)))); + dir.remove_trace("baz").await?; assert!(dir.find("baz").await?.is_none()); // Try to remove non-existent entity @@ -589,33 +651,101 @@ mod tests { } #[tokio::test] - async fn test_ops_complex_nested_hierarchy() -> FsResult<()> { - let mut root = Dir::new(MemoryStore::default()); + async fn test_ops_remove() -> FsResult<()> { + let mut dir = Dir::new(MemoryStore::default()); - // Create a complex nested structure - root.find_or_create("projects/web/index.html", true).await?; - root.find_or_create("projects/web/styles/main.css", true) - .await?; - root.find_or_create("projects/app/src/main.rs", true) - .await?; - root.find_or_create("documents/personal/notes.txt", true) - .await?; - root.find_or_create("documents/work/report.pdf", true) - .await?; + // Create entities to remove + { + let _ = dir.find_or_create("foo/bar.txt", true).await?; + let _ = dir.find_or_create("baz", false).await?; + } + + // Test remove (mark as deleted) + dir.remove("foo/bar.txt").await?; + assert!(dir.find("foo/bar.txt").await?.is_none()); // Should be skipped by find + + // Verify the entity still exists but is marked as deleted + { + let foo_dir = dir.find_mut("foo").await?.unwrap(); + if let Entity::Dir(dir) = foo_dir { + let entity = dir.get_entry("bar.txt")?.unwrap(); + assert!(entity + .resolve_entity(dir.get_store().clone()) + .await? + .get_metadata() + .get_deleted_at() + .is_some()); + } + } + + // Test remove_trace (complete removal) + dir.remove_trace("baz").await?; + assert!(dir.find("baz").await?.is_none()); + + // Verify the entity is completely gone + assert!(dir.get_entity("baz").await?.is_none()); + + // Try to remove non-existent entity + assert!(dir.remove("nonexistent").await.is_err()); + assert!(dir.remove_trace("nonexistent").await.is_err()); + + Ok(()) + } + + #[tokio::test] + async fn test_ops_find_with_deleted() -> FsResult<()> { + let mut dir = Dir::new(MemoryStore::default()); + + // Create test entities + dir.find_or_create("file1.txt", true).await?; + dir.find_or_create("file2.txt", true).await?; + dir.find_or_create("dir1", false).await?; + + // Mark file1.txt as deleted + dir.remove("file1.txt").await?; + + // Test find + assert!(dir.find("file1.txt").await?.is_none()); // Should skip deleted + assert!(matches!( + dir.find("file2.txt").await?.unwrap(), + Entity::File(_) + )); // Should find non-deleted + assert!(matches!(dir.find("dir1").await?.unwrap(), Entity::Dir(_))); // Should find non-deleted + + // Test find_mut + assert!(dir.find_mut("file1.txt").await?.is_none()); // Should skip deleted + assert!(matches!( + dir.find_mut("file2.txt").await?.unwrap(), + Entity::File(_) + )); // Should find non-deleted + assert!(matches!( + dir.find_mut("dir1").await?.unwrap(), + Entity::Dir(_) + )); // Should find non-deleted + + Ok(()) + } + + #[tokio::test] + async fn test_ops_complex_nested_hierarchy() -> FsResult<()> { + let mut root = fixtures::setup_test_filesystem().await?; // Verify the structure - assert!(matches!(root.find("projects").await?, Some(Entity::Dir(_)))); assert!(matches!( - root.find("projects/web/index.html").await?, - Some(Entity::File(_)) + root.find("projects").await?.unwrap(), + Entity::Dir(_) )); assert!(matches!( - root.find("projects/app/src/main.rs").await?, - Some(Entity::File(_)) + root.find("projects/web/index.html").await?.unwrap(), + Entity::File(_) )); assert!(matches!( - root.find("documents/work/report.pdf").await?, - Some(Entity::File(_)) + root.find("projects/app/src/main.rs").await?.unwrap(), + Entity::File(_) + )); + assert!(matches!( + root.find("documents/work/report.pdf").await?.unwrap(), + Entity::File(_) )); // List contents of directories @@ -659,32 +789,34 @@ mod tests { root.copy("documents/personal/notes.txt", "projects") .await?; assert!(matches!( - root.find("projects/notes.txt").await?, - Some(Entity::File(_)) + root.find("projects/notes.txt").await?.unwrap(), + Entity::File(_) )); - // Remove a file - let (removed_filename, _) = root.remove("documents/work/report.pdf").await?; - assert_eq!(removed_filename, "report.pdf".parse()?); + // Test both remove and remove_trace + root.remove_trace("documents/work/report.pdf").await?; assert!(root.find("documents/work/report.pdf").await?.is_none()); - // Remove a file and its parent directory + // Test remove (mark as deleted) root.remove("documents/personal/notes.txt").await?; - root.remove("documents/personal").await?; + assert!(root.find("documents/personal/notes.txt").await?.is_none()); + + // Test remove_trace again + root.remove_trace("documents/personal").await?; assert!(root.find("documents/personal").await?.is_none()); // Verify the final structure assert!(matches!( - root.find("projects/web/index.html").await?, - Some(Entity::File(_)) + root.find("projects/web/index.html").await?.unwrap(), + Entity::File(_) )); assert!(matches!( - root.find("projects/app/src/main.rs").await?, - Some(Entity::File(_)) + root.find("projects/app/src/main.rs").await?.unwrap(), + Entity::File(_) )); assert!(matches!( - root.find("projects/notes.txt").await?, - Some(Entity::File(_)) + root.find("projects/notes.txt").await?.unwrap(), + Entity::File(_) )); assert!(root.find("documents/personal").await?.is_none()); diff --git a/monofs/lib/filesystem/metadata.rs b/monofs/lib/filesystem/metadata.rs index 34ec8a5..827917e 100644 --- a/monofs/lib/filesystem/metadata.rs +++ b/monofs/lib/filesystem/metadata.rs @@ -26,15 +26,15 @@ pub const ENTITY_TYPE_KEY: &str = "monofs.entity_type"; /// The key for the modified at field in the metadata. pub const MODIFIED_AT_KEY: &str = "monofs.modified_at"; +/// The key for the deleted at field in the metadata. +pub const DELETED_AT_KEY: &str = "monofs.deleted_at"; + /// The key for the symbolic link depth field in the metadata. pub const SYMLINK_DEPTH_KEY: &str = "monofs.symlink_depth"; /// The key for the sync type field in the metadata. pub const SYNC_TYPE_KEY: &str = "monofs.sync_type"; -/// The key for the tombstone field in the metadata. -pub const TOMBSTONE_KEY: &str = "monofs.tombstone"; - //-------------------------------------------------------------------------------------------------- // Types //-------------------------------------------------------------------------------------------------- @@ -72,7 +72,10 @@ where created_at: DateTime, /// The time of the last modification of the entity. - modified_at: DateTime, + modified_at: Option>, + + /// Whether the entity has been deleted. + deleted_at: Option>, /// The maximum depth of a symbolic link. symlink_depth: u32, @@ -80,9 +83,6 @@ where /// The sync type of the entity. sync_type: SyncType, - /// Whether the entity is a tombstone. - tombstone: bool, - /// Extended attributes. #[serde(skip)] extended_attrs: Option>, @@ -124,10 +124,10 @@ pub struct ExtendedAttributes { pub struct MetadataSerializable { entity_type: EntityType, created_at: DateTime, - modified_at: DateTime, + modified_at: Option>, symlink_depth: u32, sync_type: SyncType, - tombstone: bool, + deleted_at: Option>, extended_attrs: Option, } @@ -143,46 +143,29 @@ impl Metadata where S: IpldStore, { - /// Creates a new metadata object. - /// - /// ## Examples - /// - /// ``` - /// use monofs::filesystem::{EntityType, Metadata, SyncType}; - /// use monofs::config::DEFAULT_SYMLINK_DEPTH; - /// use monoutils_store::MemoryStore; - /// - /// let store = MemoryStore::default(); - /// let metadata = Metadata::new(EntityType::File, store); - /// - /// assert_eq!(*metadata.get_entity_type(), EntityType::File); - /// assert_eq!(*metadata.get_sync_type(), SyncType::RAFT); - /// assert_eq!(*metadata.get_symlink_depth(), DEFAULT_SYMLINK_DEPTH); - /// ``` + /// Creates a new metadata instance with the given entity type and store. pub fn new(entity_type: EntityType, store: S) -> Self { - let now = Utc::now(); - Self { entity_type, - created_at: now, - modified_at: now, + created_at: Utc::now(), + modified_at: None, + deleted_at: None, symlink_depth: DEFAULT_SYMLINK_DEPTH, sync_type: SyncType::RAFT, - tombstone: false, extended_attrs: None, store, } } - /// Tries to create a new `Metadata` from a serializable representation. + /// Creates a new metadata instance from a serializable representation. pub fn from_serializable(serializable: MetadataSerializable, store: S) -> FsResult { Ok(Self { entity_type: serializable.entity_type, created_at: serializable.created_at, modified_at: serializable.modified_at, + deleted_at: serializable.deleted_at, symlink_depth: serializable.symlink_depth, sync_type: serializable.sync_type, - tombstone: serializable.tombstone, extended_attrs: serializable .extended_attrs .map(|cid| AttributesCidLink::from(cid)), @@ -190,23 +173,24 @@ where }) } - /// Gets the serializable representation of the metadata. + /// Gets a serializable representation of the metadata. pub async fn get_serializable(&self) -> FsResult where S: Send + Sync, { - let extended_attrs = match &self.extended_attrs { - Some(link) => Some(link.resolve_cid().await?), - None => None, + let extended_attrs = if let Some(attrs) = &self.extended_attrs { + Some(attrs.resolve_cid().await?) + } else { + None }; Ok(MetadataSerializable { entity_type: self.entity_type, created_at: self.created_at, modified_at: self.modified_at, + deleted_at: self.deleted_at, symlink_depth: self.symlink_depth, sync_type: self.sync_type, - tombstone: self.tombstone, extended_attrs, }) } @@ -307,6 +291,16 @@ where pub fn set_symlink_depth(&mut self, depth: u32) { self.symlink_depth = depth; } + + /// Sets the deleted_at timestamp. + pub fn set_deleted_at(&mut self, timestamp: Option>) { + self.deleted_at = timestamp; + } + + /// Sets the sync type. + pub fn set_sync_type(&mut self, sync_type: SyncType) { + self.sync_type = sync_type; + } } //-------------------------------------------------------------------------------------------------- @@ -367,9 +361,9 @@ where .field("entity_type", &self.entity_type) .field("created_at", &self.created_at) .field("modified_at", &self.modified_at) + .field("deleted_at", &self.deleted_at) .field("symlink_depth", &self.symlink_depth) .field("sync_type", &self.sync_type) - .field("tombstone", &self.tombstone) .field( "extended_attrs", &self.extended_attrs.as_ref().map(|link| link.get_cid()), @@ -401,50 +395,39 @@ mod tests { #[test] fn test_metadata_new() { let store = MemoryStore::default(); - let metadata: Metadata = Metadata::new(EntityType::File, store); + let metadata = Metadata::new(EntityType::File, store); assert_eq!(*metadata.get_entity_type(), EntityType::File); - assert_eq!(*metadata.get_symlink_depth(), DEFAULT_SYMLINK_DEPTH); assert_eq!(*metadata.get_sync_type(), SyncType::RAFT); - assert!(!metadata.get_tombstone()); + assert_eq!(*metadata.get_symlink_depth(), DEFAULT_SYMLINK_DEPTH); + assert!(metadata.get_deleted_at().is_none()); } #[test] fn test_metadata_getters() { let store = MemoryStore::default(); - let metadata = Metadata::new(EntityType::Dir, store); + let metadata = Metadata::new(EntityType::File, store); - assert_eq!(*metadata.get_entity_type(), EntityType::Dir); - assert_eq!(*metadata.get_symlink_depth(), DEFAULT_SYMLINK_DEPTH); - assert!(metadata.get_created_at() <= &Utc::now()); - assert!(metadata.get_modified_at() <= &Utc::now()); + assert_eq!(*metadata.get_entity_type(), EntityType::File); assert_eq!(*metadata.get_sync_type(), SyncType::RAFT); - assert!(!metadata.get_tombstone()); + assert_eq!(*metadata.get_symlink_depth(), DEFAULT_SYMLINK_DEPTH); + assert!(metadata.get_deleted_at().is_none()); } #[tokio::test] - async fn test_metadata_stores_loads() { + async fn test_metadata_stores_loads() -> anyhow::Result<()> { let store = MemoryStore::default(); let metadata = Metadata::new(EntityType::File, store.clone()); - let cid = metadata.store().await.unwrap(); - let loaded_metadata = Metadata::load(&cid, store).await.unwrap(); + let cid = metadata.store().await?; + let loaded = Metadata::load(&cid, store).await?; + + assert_eq!(*loaded.get_entity_type(), EntityType::File); + assert_eq!(*loaded.get_sync_type(), SyncType::RAFT); + assert_eq!(*loaded.get_symlink_depth(), DEFAULT_SYMLINK_DEPTH); + assert!(loaded.get_deleted_at().is_none()); - assert_eq!( - metadata.get_entity_type(), - loaded_metadata.get_entity_type() - ); - assert_eq!( - metadata.get_symlink_depth(), - loaded_metadata.get_symlink_depth() - ); - assert_eq!(metadata.get_sync_type(), loaded_metadata.get_sync_type()); - assert_eq!(metadata.get_tombstone(), loaded_metadata.get_tombstone()); - assert_eq!(metadata.get_created_at(), loaded_metadata.get_created_at()); - assert_eq!( - metadata.get_modified_at(), - loaded_metadata.get_modified_at() - ); + Ok(()) } #[tokio::test]