use std::{
fs,
io,
io::{
Seek,
SeekFrom,
Write,
},
path::{
Path,
PathBuf,
},
};
pub trait Vfs {
fn create_dir_all(&mut self, path: &Path) -> io::Result<()>;
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>>;
fn write(&mut self, path: &Path, bytes: &[u8]) -> io::Result<()>;
fn write_at(&mut self, path: &Path, offset: u64, bytes: &[u8]) -> io::Result<()>;
fn fsync_file(&mut self, path: &Path) -> io::Result<()>;
fn rename(&mut self, from: &Path, to: &Path) -> io::Result<()>;
fn remove(&mut self, path: &Path) -> io::Result<()>;
fn list(&mut self, dir: &Path) -> io::Result<Vec<PathBuf>>;
fn fsync_dir(&mut self, path: &Path) -> io::Result<()>;
fn exists(&mut self, path: &Path) -> io::Result<bool>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct RealVfs;
impl Vfs for RealVfs {
fn create_dir_all(&mut self, path: &Path) -> io::Result<()> {
fs::create_dir_all(path)
}
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>> {
fs::read(path)
}
fn write(&mut self, path: &Path, bytes: &[u8]) -> io::Result<()> {
fs::write(path, bytes)
}
fn write_at(&mut self, path: &Path, offset: u64, bytes: &[u8]) -> io::Result<()> {
let mut file = fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(false)
.open(path)?;
file.seek(SeekFrom::Start(offset))?;
file.write_all(bytes)
}
fn fsync_file(&mut self, path: &Path) -> io::Result<()> {
fs::File::open(path)?.sync_all()
}
fn rename(&mut self, from: &Path, to: &Path) -> io::Result<()> {
fs::rename(from, to)
}
fn remove(&mut self, path: &Path) -> io::Result<()> {
fs::remove_file(path)
}
fn list(&mut self, dir: &Path) -> io::Result<Vec<PathBuf>> {
fs::read_dir(dir)?
.map(|entry| entry.map(|e| e.path()))
.collect()
}
fn fsync_dir(&mut self, path: &Path) -> io::Result<()> {
match fs::File::open(path)?.sync_all() {
Err(error)
if matches!(
error.kind(),
io::ErrorKind::InvalidInput | io::ErrorKind::Unsupported
) =>
{
Ok(())
}
result => result,
}
}
fn exists(&mut self, path: &Path) -> io::Result<bool> {
path.try_exists()
}
}
#[cfg(test)]
mod tests {
use tempfile::tempdir;
use super::{
RealVfs,
Vfs,
};
#[test]
fn real_vfs_round_trips_files() {
let dir = tempdir().unwrap();
let mut vfs = RealVfs;
let original = dir.path().join("original.bin");
let renamed = dir.path().join("renamed.bin");
let bytes = b"chainfold storage".to_vec();
vfs.write(&original, &bytes).unwrap();
vfs.fsync_file(&original).unwrap();
vfs.rename(&original, &renamed).unwrap();
vfs.fsync_dir(dir.path()).unwrap();
let listed = vfs.list(dir.path()).unwrap();
let read_back = vfs.read(&renamed).unwrap();
assert!(!vfs.exists(&original).unwrap());
assert!(vfs.exists(&renamed).unwrap());
assert_eq!(listed, vec![renamed.clone()]);
assert_eq!(read_back, bytes);
}
}