Skip to content

Commit

Permalink
feat(monofs): implement read, write, remove, rename and readdir opera…
Browse files Browse the repository at this point in the history
…tions

- Add read/write support with offset and count
- Add file/directory removal with deletion tracking
- Add rename/move operations between directories
- Add directory listing with pagination
- Add extensive test coverage for new operations
  • Loading branch information
appcypher committed Jan 14, 2025
1 parent eb9fc12 commit 609ef22
Show file tree
Hide file tree
Showing 8 changed files with 954 additions and 216 deletions.
8 changes: 7 additions & 1 deletion .todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@
- [ ] `IpldStore::get_cid_size(cid: &Cid) -> Result<u64, Error>`?
- [ ] `Layout::get_cid_size(cid: &Cid) -> Result<u64, Error>`?

- [ ] Implement seeking for writes in `IpldStoreSeekable`
- [ ] Figure out how Chunker and Layout can support seeking writes.
- [ ] Introduce `put_bytes_seekable`.

- [ ] Children entities should inherit sync type from their parents when they get added.

- [ ] Change MonofsServer `fileid_map` and `path_map` to use LruCache instead of HashMap.
- [ ] What if we use LruCache for MonofsServer `fileid_map` and `path_map`?

- [ ] The issue that comes to mind is that we hand these fileids to clients and they may expect them to be stable.

## monoutils-store

Expand Down
11 changes: 7 additions & 4 deletions monofs/examples/file_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
//! cargo run --example file_ops
//! ```
use monofs::filesystem::{File, FileInputStream, FileOutputStream};
use monofs::filesystem::File;
use monoutils_store::{MemoryStore, Storable};
use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};

Expand All @@ -40,21 +40,20 @@ async fn main() -> anyhow::Result<()> {

// Write content to the file
let content = b"Hello, monofs!";
let mut output_stream = FileOutputStream::new(&mut file);
let mut output_stream = file.get_output_stream();
output_stream.write_all(content).await?;
output_stream.shutdown().await?;
println!("Wrote content to file");

// Read content from the file
let input_stream = FileInputStream::new(&file).await?;
let input_stream = file.get_input_stream().await?;
let mut buffer = Vec::new();
let mut reader = BufReader::new(input_stream);
reader.read_to_end(&mut buffer).await?;
println!(
"Read content from file: {}",
String::from_utf8_lossy(&buffer)
);
drop(reader); // Drop reader to free up the input stream ref to the file

// Check if the file is empty
println!("File is empty: {}", file.is_empty());
Expand All @@ -71,8 +70,12 @@ async fn main() -> anyhow::Result<()> {
let loaded_file = File::load(&file_cid, store).await?;
println!("Loaded file: {:?}", loaded_file);

// Drop reader to free up reference to the file
drop(reader);

// Truncate the file
file.truncate();

println!("Truncated file");
println!("File is empty after truncation: {}", file.is_empty());

Expand Down
45 changes: 45 additions & 0 deletions monofs/lib/filesystem/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ where

/// Checks if an [`EntityCidLink`] with the given name exists in the directory.
///
/// **Important**: This method returns true even if the entry has been marked as deleted.
/// To check for deleted entries, use [`has_entity`][`Dir::has_entity`] instead which respects the deleted status.
///
/// ## Examples
///
/// ```
Expand Down Expand Up @@ -290,6 +293,9 @@ where

/// Gets the [`EntityCidLink`] with the given name from the directory's entries.
///
/// **Important**: This method returns the entry even if it has been marked as deleted.
/// To check for deleted entries, use [`get_entity`][`Dir::get_entity`] instead which respects the deleted status.
///
/// ## Examples
///
/// ```
Expand Down Expand Up @@ -319,6 +325,9 @@ where
}

/// Gets the [`EntityCidLink`] with the given name from the directory's entries.
///
/// **Important**: This method returns the entry even if it has been marked as deleted.
/// To check for deleted entries, use [`get_entity_mut`][`Dir::get_entity_mut`] instead which respects the deleted status.
#[inline]
pub fn get_entry_mut(
&mut self,
Expand All @@ -330,6 +339,10 @@ where
}

/// Gets the [`Entity`] with the associated name from the directory's entries.
///
/// **Note**: This method respects the deleted status of entities. If an entity exists but has been
/// marked as deleted (has a `deleted_at` timestamp), this method will return `None`. To access
/// deleted entities, use [`get_entry`][`Dir::get_entry`] instead.
pub async fn get_entity(&self, name: impl AsRef<str>) -> FsResult<Option<&Entity<S>>>
where
S: Send + Sync,
Expand All @@ -347,7 +360,23 @@ where
}
}

/// Checks if an [`Entity`] with the given name exists in the directory.
///
/// **Note**: This method respects the deleted status of entities. If an entity exists but has been
/// marked as deleted (has a `deleted_at` timestamp), this method will return `false`. To check for
/// existence regardless of deleted status, use [`has_entry`][`Dir::has_entry`] instead.
pub async fn has_entity(&self, name: impl AsRef<str>) -> FsResult<bool>
where
S: Send + Sync,
{
Ok(self.get_entity(name).await?.is_some())
}

/// Gets the [`Entity`] with the associated name from the directory's entries.
///
/// **Note**: This method respects the deleted status of entities. If an entity exists but has been
/// marked as deleted (has a `deleted_at` timestamp), this method will return `None`. To access
/// deleted entities, use [`get_entry_mut`][`Dir::get_entry_mut`] instead.
pub async fn get_entity_mut(
&mut self,
name: impl AsRef<str>,
Expand All @@ -370,6 +399,10 @@ where
}

/// Gets the [`Dir`] with the associated name from the directory's entries.
///
/// **Note**: This method respects the deleted status of entities. If the directory exists but has been
/// marked as deleted (has a `deleted_at` timestamp), this method will return `None`. To access
/// deleted directories, use [`get_entry`][`Dir::get_entry`] instead.
pub async fn get_dir(&self, name: impl AsRef<str>) -> FsResult<Option<&Dir<S>>>
where
S: Send + Sync,
Expand All @@ -381,6 +414,10 @@ where
}

/// Gets the [`Dir`] with the associated name from the directory's entries.
///
/// **Note**: This method respects the deleted status of entities. If the directory exists but has been
/// marked as deleted (has a `deleted_at` timestamp), this method will return `None`. To access
/// deleted directories, use [`get_entry_mut`][`Dir::get_entry_mut`] instead.
pub async fn get_dir_mut(&mut self, name: impl AsRef<str>) -> FsResult<Option<&mut Dir<S>>>
where
S: Send + Sync,
Expand All @@ -392,6 +429,10 @@ where
}

/// Gets the [`File`] with the associated name from the directory's entries.
///
/// **Note**: This method respects the deleted status of entities. If the file exists but has been
/// marked as deleted (has a `deleted_at` timestamp), this method will return `None`. To access
/// deleted files, use [`get_entry`][`Dir::get_entry`] instead.
pub async fn get_file(&self, name: impl AsRef<str>) -> FsResult<Option<&File<S>>>
where
S: Send + Sync,
Expand All @@ -403,6 +444,10 @@ where
}

/// Gets the [`File`] with the associated name from the directory's entries.
///
/// **Note**: This method respects the deleted status of entities. If the file exists but has been
/// marked as deleted (has a `deleted_at` timestamp), this method will return `None`. To access
/// deleted files, use [`get_entry_mut`][`Dir::get_entry_mut`] instead.
pub async fn get_file_mut(&mut self, name: impl AsRef<str>) -> FsResult<Option<&mut File<S>>>
where
S: Send + Sync,
Expand Down
18 changes: 18 additions & 0 deletions monofs/lib/filesystem/file/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ where
// Methods
//--------------------------------------------------------------------------------------------------

impl<S> File<S>
where
S: IpldStore + Send + Sync + 'static,
{
/// Gets an input stream for reading the file's content.
pub async fn get_input_stream(&self) -> io::Result<FileInputStream<'_>>
where
S: IpldStoreSeekable,
{
FileInputStream::new(self).await
}

/// Gets an output stream for writing to the file.
pub fn get_output_stream(&mut self) -> FileOutputStream<'_, S> {
FileOutputStream::new(self)
}
}

impl<'a> FileInputStream<'a> {
/// Creates a new `FileInputStream` from a `File`.
pub async fn new<S>(file: &'a File<S>) -> io::Result<Self>
Expand Down
Loading

0 comments on commit 609ef22

Please sign in to comment.