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: allow vfs implementations to specify m/b/a-times in file metadata #63

Merged
merged 14 commits into from
Mar 9, 2024
Merged
2 changes: 1 addition & 1 deletion .github/workflows/compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- stable
- beta
- nightly
- 1.63.0 # MSRV
- 1.75.0 # MSRV

steps:
- uses: actions/checkout@v2
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ readme = "README.md"
keywords = ["vfs", "virtual", "filesystem", "async"]
license = "Apache-2.0"
edition = "2021"
rust-version = "1.75"
manuel-woelker marked this conversation as resolved.
Show resolved Hide resolved

[badges]
travis-ci = { repository = "manuel-woelker/rust-vfs", branch = "master" }
Expand Down
15 changes: 14 additions & 1 deletion src/async_vfs/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

use crate::async_vfs::{AsyncVfsPath, SeekAndRead};
use crate::error::VfsErrorKind;
use crate::{VfsMetadata, VfsResult};
use crate::{VfsError, VfsMetadata, VfsResult};

use async_std::io::Write;
use async_std::stream::Stream;
use async_trait::async_trait;
use std::fmt::Debug;
use std::time::SystemTime;

/// File system implementations must implement this trait
/// All path parameters are absolute, starting with '/', except for the root directory
Expand Down Expand Up @@ -36,6 +37,18 @@ pub trait AsyncFileSystem: Debug + Sync + Send + 'static {
async fn append_file(&self, path: &str) -> VfsResult<Box<dyn Write + Send + Unpin>>;
/// Returns the file metadata for the file at this path
async fn metadata(&self, path: &str) -> VfsResult<VfsMetadata>;
/// Sets the files creation timestamp, if the implementation supports it
async fn set_creation_time(&self, _path: &str, _time: SystemTime) -> VfsResult<()> {
Err(VfsError::from(VfsErrorKind::NotSupported))
}
/// Sets the files modification timestamp, if the implementation supports it
async fn set_modification_time(&self, _path: &str, _time: SystemTime) -> VfsResult<()> {
Err(VfsError::from(VfsErrorKind::NotSupported))
}
/// Sets the files access timestamp, if the implementation supports it
async fn set_access_time(&self, _path: &str, _time: SystemTime) -> VfsResult<()> {
Err(VfsError::from(VfsErrorKind::NotSupported))
}
/// Returns true if a file or directory at path exists, false otherwise
async fn exists(&self, path: &str) -> VfsResult<bool>;
/// Removes the file at this path
Expand Down
13 changes: 13 additions & 0 deletions src/async_vfs/impls/altroot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use crate::async_vfs::{AsyncFileSystem, AsyncVfsPath, SeekAndRead};
use crate::{error::VfsErrorKind, VfsMetadata, VfsResult};
use std::time::SystemTime;

use async_std::io::Write;
use async_trait::async_trait;
Expand Down Expand Up @@ -71,6 +72,18 @@ impl AsyncFileSystem for AsyncAltrootFS {
self.path(path)?.metadata().await
}

async fn set_creation_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
self.path(path)?.set_creation_time(time).await
}

async fn set_modification_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
self.path(path)?.set_modification_time(time).await
}

async fn set_access_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
self.path(path)?.set_access_time(time).await
}

async fn exists(&self, path: &str) -> VfsResult<bool> {
match self.path(path) {
Ok(p) => p.exists().await,
Expand Down
3 changes: 3 additions & 0 deletions src/async_vfs/impls/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ impl AsyncFileSystem for AsyncMemoryFS {
Ok(VfsMetadata {
file_type: file.file_type,
len: file.content.len() as u64,
modified: None,
created: None,
accessed: None,
})
}

Expand Down
13 changes: 13 additions & 0 deletions src/async_vfs/impls/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use async_std::io::Write;
use async_trait::async_trait;
use futures::stream::{Stream, StreamExt};
use std::collections::HashSet;
use std::time::SystemTime;

/// An overlay file system combining several filesystems into one, an upper layer with read/write access and lower layers with only read access
///
Expand Down Expand Up @@ -154,6 +155,18 @@ impl AsyncFileSystem for AsyncOverlayFS {
self.read_path(path).await?.metadata().await
}

async fn set_creation_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
self.write_path(path)?.set_creation_time(time).await
}

async fn set_modification_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
self.write_path(path)?.set_modification_time(time).await
}

async fn set_access_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
self.write_path(path)?.set_access_time(time).await
}

async fn exists(&self, path: &str) -> VfsResult<bool> {
if self
.whiteout_path(path)
Expand Down
6 changes: 6 additions & 0 deletions src/async_vfs/impls/physical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,17 @@ impl AsyncFileSystem for AsyncPhysicalFS {
VfsMetadata {
file_type: VfsFileType::Directory,
len: 0,
modified: metadata.modified().ok(),
created: metadata.created().ok(),
accessed: metadata.accessed().ok(),
}
} else {
VfsMetadata {
file_type: VfsFileType::File,
len: metadata.len(),
modified: metadata.modified().ok(),
created: metadata.created().ok(),
accessed: metadata.accessed().ok(),
}
})
}
Expand Down
91 changes: 91 additions & 0 deletions src/async_vfs/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use async_std::sync::Arc;
use async_std::task::{Context, Poll};
use futures::{future::BoxFuture, FutureExt, Stream, StreamExt};
use std::pin::Pin;
use std::time::SystemTime;

/// Trait combining Seek and Read, return value for opening files
pub trait SeekAndRead: Seek + Read {}
Expand Down Expand Up @@ -467,6 +468,96 @@ impl AsyncVfsPath {
})
}

/// Sets the files creation timestamp at this path
///
/// ```
/// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
/// use vfs::{VfsError, VfsFileType, VfsMetadata, VfsPath};
/// use async_std::io::WriteExt;
/// # tokio_test::block_on(async {
/// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
/// let file = path.join("foo.txt")?;
/// file.create_file();
///
/// let time = std::time::SystemTime::now();
/// file.set_creation_time(time).await?;
///
/// assert_eq!(file.metadata().await?.len, 0);
/// assert_eq!(file.metadata().await?.created, Some(time));
///
/// # Ok::<(), VfsError>(())
/// # });
pub async fn set_creation_time(&self, time: SystemTime) -> VfsResult<()> {
self.fs
.fs
.set_creation_time(&self.path, time)
.await
.map_err(|err| {
err.with_path(&*self.path)
.with_context(|| "Could not set creation timestamp.")
})
}

/// Sets the files modification timestamp at this path
///
/// ```
/// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
/// use vfs::{VfsError, VfsFileType, VfsMetadata, VfsPath};
/// use async_std::io::WriteExt;
/// # tokio_test::block_on(async {
/// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
/// let file = path.join("foo.txt")?;
/// file.create_file();
///
/// let time = std::time::SystemTime::now();
/// file.set_modification_time(time).await?;
///
/// assert_eq!(file.metadata().await?.len, 0);
/// assert_eq!(file.metadata().await?.modified, Some(time));
///
/// # Ok::<(), VfsError>(())
/// # });
pub async fn set_modification_time(&self, time: SystemTime) -> VfsResult<()> {
self.fs
.fs
.set_modification_time(&self.path, time)
.await
.map_err(|err| {
err.with_path(&*self.path)
.with_context(|| "Could not set modification timestamp.")
})
}

/// Sets the files access timestamp at this path
///
/// ```
/// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath};
/// use vfs::{VfsError, VfsFileType, VfsMetadata, VfsPath};
/// use async_std::io::WriteExt;
/// # tokio_test::block_on(async {
/// let path = AsyncVfsPath::new(AsyncMemoryFS::new());
/// let file = path.join("foo.txt")?;
/// file.create_file();
///
/// let time = std::time::SystemTime::now();
/// file.set_access_time(time).await?;
///
/// assert_eq!(file.metadata().await?.len, 0);
/// assert_eq!(file.metadata().await?.accessed, Some(time));
///
/// # Ok::<(), VfsError>(())
/// # });
pub async fn set_access_time(&self, time: SystemTime) -> VfsResult<()> {
self.fs
.fs
.set_access_time(&self.path, time)
.await
.map_err(|err| {
err.with_path(&*self.path)
.with_context(|| "Could not set access timestamp.")
})
}

/// Returns `true` if the path exists and is pointing at a regular file, otherwise returns `false`.
///
/// Note that this call may fail if the file's existence cannot be determined or the metadata can not be retrieved
Expand Down
73 changes: 73 additions & 0 deletions src/async_vfs/test_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ macro_rules! test_async_vfs {
use $crate::VfsResult;
use futures::stream::StreamExt;
use async_std::io::{WriteExt, ReadExt};
use std::time::SystemTime;

fn create_root() -> AsyncVfsPath {
$root.into()
Expand All @@ -21,6 +22,78 @@ macro_rules! test_async_vfs {
create_root();
}

#[tokio::test]
async fn set_and_query_creation_timestamp() -> VfsResult<()> {
let root = create_root();
let path = root.join("foobar.txt").unwrap();
drop( path.create_file().await.unwrap() );

let time = SystemTime::now();
let result = path.set_creation_time(time).await;

match result {
Err(err) => {
if let VfsErrorKind::NotSupported = err.kind() {
println!("Skipping creation time test: set_creation_time unsupported!");
} else {
return Err(err);
}
},
_ => {
assert_eq!(path.metadata().await?.created, Some(time));
}
}
Ok(())
}

#[tokio::test]
async fn set_and_query_modification_timestamp() -> VfsResult<()> {
let root = create_root();
let path = root.join("foobar.txt").unwrap();
drop( path.create_file().await.unwrap() );

let time = SystemTime::now();
let result = path.set_modification_time(time).await;

match result {
Err(err) => {
if let VfsErrorKind::NotSupported = err.kind() {
println!("Skipping creation time test: set_modification_time unsupported!");
} else {
return Err(err);
}
},
_ => {
assert_eq!(path.metadata().await?.modified, Some(time));
}
}
Ok(())
}

#[tokio::test]
async fn set_and_query_access_timestamp() -> VfsResult<()> {
let root = create_root();
let path = root.join("foobar.txt").unwrap();
drop( path.create_file().await.unwrap() );

let time = SystemTime::now();
let result = path.set_access_time(time).await;

match result {
Err(err) => {
if let VfsErrorKind::NotSupported = err.kind() {
println!("Skipping access time test: set_access_time unsupported!");
} else {
return Err(err);
}
},
_ => {
assert_eq!(path.metadata().await?.accessed, Some(time));
}
}
Ok(())
}

#[tokio::test]
async fn write_and_read_file() -> VfsResult<()>{
let root = create_root();
Expand Down
15 changes: 14 additions & 1 deletion src/filesystem.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! The filesystem trait definitions needed to implement new virtual filesystems

use crate::error::VfsErrorKind;
use crate::{SeekAndRead, VfsMetadata, VfsPath, VfsResult};
use crate::{SeekAndRead, VfsError, VfsMetadata, VfsPath, VfsResult};
use std::fmt::Debug;
use std::io::Write;
use std::time::SystemTime;

/// File system implementations must implement this trait
/// All path parameters are absolute, starting with '/', except for the root directory
Expand All @@ -28,6 +29,18 @@ pub trait FileSystem: Debug + Sync + Send + 'static {
fn append_file(&self, path: &str) -> VfsResult<Box<dyn Write + Send>>;
/// Returns the file metadata for the file at this path
fn metadata(&self, path: &str) -> VfsResult<VfsMetadata>;
/// Sets the files creation timestamp, if the implementation supports it
fn set_creation_time(&self, _path: &str, _time: SystemTime) -> VfsResult<()> {
Err(VfsError::from(VfsErrorKind::NotSupported))
}
/// Sets the files modification timestamp, if the implementation supports it
fn set_modification_time(&self, _path: &str, _time: SystemTime) -> VfsResult<()> {
Err(VfsError::from(VfsErrorKind::NotSupported))
}
/// Sets the files access timestamp, if the implementation supports it
fn set_access_time(&self, _path: &str, _time: SystemTime) -> VfsResult<()> {
Err(VfsError::from(VfsErrorKind::NotSupported))
}
/// Returns true if a file or directory at path exists, false otherwise
fn exists(&self, path: &str) -> VfsResult<bool>;
/// Removes the file at this path
Expand Down
13 changes: 13 additions & 0 deletions src/impls/altroot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use crate::{error::VfsErrorKind, FileSystem, SeekAndRead, VfsMetadata, VfsPath, VfsResult};
use std::io::Write;
use std::time::SystemTime;

/// Similar to a chroot but done purely by path manipulation
///
Expand Down Expand Up @@ -62,6 +63,18 @@ impl FileSystem for AltrootFS {
self.path(path)?.metadata()
}

fn set_creation_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
self.path(path)?.set_creation_time(time)
}

fn set_modification_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
self.path(path)?.set_modification_time(time)
}

fn set_access_time(&self, path: &str, time: SystemTime) -> VfsResult<()> {
self.path(path)?.set_access_time(time)
}

fn exists(&self, path: &str) -> VfsResult<bool> {
self.path(path)
.map(|path| path.exists())
Expand Down
Loading
Loading