use std::{fmt::Debug, io::Write, path::PathBuf};
#[cfg(feature = "fslock")]
use std::collections::HashMap;
use relative_path::{RelativePath, RelativePathBuf};
use vfs::{MemoryFS, VfsFileType, VfsPath};
use crate::error::Error;
pub trait Vfs {
fn lock(&mut self, path: &str) -> Result<(), Error>;
fn unlock(&mut self, path: &str) -> Result<(), Error>;
fn read(&self, path: &str) -> Result<Vec<u8>, Error>;
fn write(&mut self, path: &str, data: &[u8], sync_option: VfsSyncOption) -> Result<(), Error>;
fn sync_file(&mut self, path: &str, sync_option: VfsSyncOption) -> Result<(), Error>;
fn remove_file(&mut self, path: &str) -> Result<(), Error>;
fn read_dir(&self, path: &str) -> Result<Vec<String>, Error>;
fn create_dir(&mut self, path: &str) -> Result<(), Error>;
fn create_dir_all(&mut self, path: &str) -> Result<(), Error> {
let mut current_path = RelativePathBuf::default();
for part in RelativePath::new(path).components() {
current_path.push(part.as_str());
if !self.exists(current_path.as_str())? {
self.create_dir(current_path.as_str())?;
}
}
Ok(())
}
fn remove_dir(&mut self, path: &str) -> Result<(), Error>;
fn remove_empty_dir_all(&mut self, path: &str) -> Result<(), Error> {
let mut current_path = RelativePathBuf::from(path);
loop {
if current_path.as_str() != "" && self.read_dir(current_path.as_str())?.is_empty() {
self.remove_dir(current_path.as_str())?;
} else {
break;
}
if let Some(parent) = current_path.parent() {
current_path = parent.to_owned();
} else {
break;
}
}
Ok(())
}
fn rename_file(&mut self, old_path: &str, new_path: &str) -> Result<(), Error>;
fn is_dir(&self, path: &str) -> Result<bool, Error>;
fn exists(&self, path: &str) -> Result<bool, Error>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VfsSyncOption {
None,
Data,
All,
}
impl Default for VfsSyncOption {
fn default() -> Self {
Self::None
}
}
#[derive(Clone)]
pub struct MemoryVfs {
vfs: VfsPath,
}
impl MemoryVfs {
pub fn new() -> Self {
Self {
vfs: VfsPath::new(MemoryFS::default()),
}
}
}
impl Default for MemoryVfs {
fn default() -> Self {
Self::new()
}
}
impl Debug for MemoryVfs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MemoryVfs")
}
}
impl Vfs for MemoryVfs {
fn lock(&mut self, _path: &str) -> Result<(), Error> {
Ok(())
}
fn unlock(&mut self, _path: &str) -> Result<(), Error> {
Ok(())
}
fn read(&self, path: &str) -> Result<Vec<u8>, Error> {
let mut file = self.vfs.join(path)?.open_file()?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
Ok(buffer)
}
fn write(&mut self, path: &str, data: &[u8], _sync_option: VfsSyncOption) -> Result<(), Error> {
let mut file = self.vfs.join(path)?.create_file()?;
file.write_all(data)?;
Ok(())
}
fn sync_file(&mut self, _path: &str, _sync_option: VfsSyncOption) -> Result<(), Error> {
Ok(())
}
fn remove_file(&mut self, path: &str) -> Result<(), Error> {
self.vfs.join(path)?.remove_file()?;
Ok(())
}
fn read_dir(&self, path: &str) -> Result<Vec<String>, Error> {
let mut filenames = Vec::new();
for sub_path in self.vfs.join(path)?.read_dir()? {
filenames.push(sub_path.filename());
}
Ok(filenames)
}
fn create_dir(&mut self, path: &str) -> Result<(), Error> {
self.vfs.join(path)?.create_dir()?;
Ok(())
}
fn remove_dir(&mut self, path: &str) -> Result<(), Error> {
self.vfs.join(path)?.remove_dir()?;
Ok(())
}
fn rename_file(&mut self, old_path: &str, new_path: &str) -> Result<(), Error> {
if self.exists(new_path)? {
self.remove_file(new_path)?;
}
self.vfs
.join(old_path)?
.move_file(&self.vfs.join(new_path)?)?;
Ok(())
}
fn is_dir(&self, path: &str) -> Result<bool, Error> {
let metadata = self.vfs.join(path)?.metadata()?;
Ok(matches!(metadata.file_type, VfsFileType::Directory))
}
fn exists(&self, path: &str) -> Result<bool, Error> {
Ok(self.vfs.join(path)?.exists()?)
}
}
#[cfg(feature = "fslock")]
type LockFileType = fslock::LockFile;
pub struct OsVfs {
root: PathBuf,
#[cfg(feature = "fslock")]
locks: HashMap<PathBuf, LockFileType>,
}
impl OsVfs {
pub fn new<P>(root: P) -> Self
where
P: Into<PathBuf>,
{
Self {
root: root.into(),
#[cfg(feature = "fslock")]
locks: HashMap::new(),
}
}
}
impl Debug for OsVfs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "OsVfs {{ path: {:?} }}", &self.root)
}
}
impl Vfs for OsVfs {
#[cfg(feature = "fslock")]
fn lock(&mut self, path: &str) -> Result<(), Error> {
let mut lock = fslock::LockFile::open(self.root.join(path).as_path())?;
if !lock.try_lock()? {
return Err(Error::Locked);
}
self.locks.insert(self.root.join(path), lock);
Ok(())
}
#[cfg(not(feature = "fslock"))]
fn lock(&mut self, _path: &str) -> Result<(), Error> {
Err(Error::FileLockingUnavailable)
}
#[cfg(feature = "fslock")]
fn unlock(&mut self, path: &str) -> Result<(), Error> {
if let Some(mut lock) = self.locks.remove(&self.root.join(path)) {
lock.unlock()?;
} else {
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"file not locked",
)));
}
Ok(())
}
#[cfg(not(feature = "fslock"))]
fn unlock(&mut self, _path: &str) -> Result<(), Error> {
Err(Error::FileLockingUnavailable)
}
fn read(&self, path: &str) -> Result<Vec<u8>, Error> {
Ok(std::fs::read(self.root.join(path))?)
}
fn write(&mut self, path: &str, data: &[u8], sync_option: VfsSyncOption) -> Result<(), Error> {
match sync_option {
VfsSyncOption::None => Ok(std::fs::write(self.root.join(path), data)?),
VfsSyncOption::Data => {
let mut file = std::fs::File::create(self.root.join(path))?;
file.write_all(&data)?;
file.sync_data()?;
Ok(())
}
VfsSyncOption::All => {
let mut file = std::fs::File::create(self.root.join(path))?;
file.write_all(&data)?;
file.sync_all()?;
Ok(())
}
}
}
fn sync_file(&mut self, path: &str, sync_option: VfsSyncOption) -> Result<(), Error> {
let file = std::fs::OpenOptions::new()
.append(true)
.open(self.root.join(path))?;
match sync_option {
VfsSyncOption::None => {}
VfsSyncOption::Data => {
file.sync_data()?;
}
VfsSyncOption::All => {
file.sync_all()?;
}
}
Ok(())
}
fn remove_file(&mut self, path: &str) -> Result<(), Error> {
Ok(std::fs::remove_file(self.root.join(path))?)
}
fn read_dir(&self, path: &str) -> Result<Vec<String>, Error> {
let dir = std::fs::read_dir(self.root.join(path))?;
let mut filenames = Vec::new();
for entry in dir {
let entry = entry?;
if let Ok(filename) = entry.file_name().into_string() {
filenames.push(filename);
}
}
Ok(filenames)
}
fn create_dir(&mut self, path: &str) -> Result<(), Error> {
std::fs::create_dir(self.root.join(path))?;
Ok(())
}
fn remove_dir(&mut self, path: &str) -> Result<(), Error> {
std::fs::remove_dir(self.root.join(path))?;
Ok(())
}
fn rename_file(&mut self, old_path: &str, new_path: &str) -> Result<(), Error> {
std::fs::rename(self.root.join(old_path), self.root.join(new_path))?;
Ok(())
}
fn is_dir(&self, path: &str) -> Result<bool, Error> {
let metadata = std::fs::metadata(self.root.join(path))?;
Ok(metadata.is_dir())
}
fn exists(&self, path: &str) -> Result<bool, Error> {
Ok(self.root.join(path).exists())
}
}
pub struct ReadOnlyVfs {
inner: Box<dyn Vfs + Sync + Send>,
}
impl ReadOnlyVfs {
pub fn new(inner: Box<dyn Vfs + Sync + Send>) -> Self {
Self { inner }
}
pub fn into_inner(self) -> Box<dyn Vfs + Sync + Send> {
self.inner
}
}
impl Vfs for ReadOnlyVfs {
fn lock(&mut self, path: &str) -> Result<(), Error> {
self.inner.lock(path)
}
fn unlock(&mut self, path: &str) -> Result<(), Error> {
self.inner.unlock(path)
}
fn read(&self, path: &str) -> Result<Vec<u8>, Error> {
self.inner.read(path)
}
fn write(
&mut self,
_path: &str,
_data: &[u8],
_sync_option: VfsSyncOption,
) -> Result<(), Error> {
Err(Error::ReadOnly)
}
fn sync_file(&mut self, _path: &str, _sync_option: VfsSyncOption) -> Result<(), Error> {
Err(Error::ReadOnly)
}
fn remove_file(&mut self, _path: &str) -> Result<(), Error> {
Err(Error::ReadOnly)
}
fn read_dir(&self, path: &str) -> Result<Vec<String>, Error> {
self.inner.read_dir(path)
}
fn create_dir(&mut self, _path: &str) -> Result<(), Error> {
Err(Error::ReadOnly)
}
fn remove_dir(&mut self, _path: &str) -> Result<(), Error> {
Err(Error::ReadOnly)
}
fn rename_file(&mut self, _old_path: &str, _new_path: &str) -> Result<(), Error> {
Err(Error::ReadOnly)
}
fn is_dir(&self, path: &str) -> Result<bool, Error> {
self.inner.is_dir(path)
}
fn exists(&self, path: &str) -> Result<bool, Error> {
self.inner.exists(path)
}
}
impl Debug for ReadOnlyVfs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ReadOnlyVfs")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_recursive_helpers() {
let mut vfs = MemoryVfs::new();
vfs.create_dir_all("a/b/c").unwrap();
vfs.write(
"a/b/c/my_file",
"hello world!".as_bytes(),
VfsSyncOption::None,
)
.unwrap();
vfs.remove_empty_dir_all("a/b/c").unwrap();
assert!(vfs.exists("a/b/c").unwrap());
vfs.remove_file("a/b/c/my_file").unwrap();
vfs.remove_empty_dir_all("a/b/c").unwrap();
assert!(!vfs.exists("a/b/c").unwrap());
}
}