use async_trait::async_trait;
use std::io::Error as IoError;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use super::backend::FsBackend;
use super::limits::{FsLimits, FsUsage};
use super::traits::{DirEntry, FileSystem, FileSystemExt, Metadata, fs_errors};
use crate::error::Result;
pub struct PosixFs<B: FsBackend> {
backend: B,
}
impl<B: FsBackend> PosixFs<B> {
pub fn new(backend: B) -> Self {
Self { backend }
}
pub fn backend(&self) -> &B {
&self.backend
}
async fn check_parent_exists(&self, path: &Path) -> Result<()> {
if let Some(parent) = path.parent()
&& parent != Path::new("/")
&& parent != Path::new("")
&& !self.backend.exists(parent).await?
{
return Err(fs_errors::parent_not_found());
}
Ok(())
}
}
#[async_trait]
impl<B: FsBackend + 'static> FileSystem for PosixFs<B> {
async fn read_file(&self, path: &Path) -> Result<Vec<u8>> {
if let Ok(meta) = self.backend.stat(path).await
&& meta.file_type.is_dir()
{
return Err(fs_errors::is_a_directory());
}
self.backend.read(path).await
}
async fn write_file(&self, path: &Path, content: &[u8]) -> Result<()> {
self.check_parent_exists(path).await?;
if let Ok(meta) = self.backend.stat(path).await
&& meta.file_type.is_dir()
{
return Err(fs_errors::is_a_directory());
}
self.backend.write(path, content).await
}
async fn append_file(&self, path: &Path, content: &[u8]) -> Result<()> {
if let Ok(meta) = self.backend.stat(path).await
&& meta.file_type.is_dir()
{
return Err(fs_errors::is_a_directory());
}
self.backend.append(path, content).await
}
async fn mkdir(&self, path: &Path, recursive: bool) -> Result<()> {
if let Ok(meta) = self.backend.stat(path).await {
if meta.file_type.is_dir() {
if recursive {
return Ok(()); } else {
return Err(fs_errors::already_exists("directory exists"));
}
} else {
return Err(fs_errors::already_exists("file exists"));
}
}
if recursive {
if let Some(parent) = path.parent() {
let mut current = PathBuf::from("/");
for component in parent.components().skip(1) {
current.push(component);
if let Ok(meta) = self.backend.stat(¤t).await
&& !meta.file_type.is_dir()
{
return Err(fs_errors::already_exists("file exists"));
}
}
}
} else {
self.check_parent_exists(path).await?;
}
self.backend.mkdir(path, recursive).await
}
async fn remove(&self, path: &Path, recursive: bool) -> Result<()> {
self.backend.remove(path, recursive).await
}
async fn stat(&self, path: &Path) -> Result<Metadata> {
self.backend.stat(path).await
}
async fn read_dir(&self, path: &Path) -> Result<Vec<DirEntry>> {
if let Ok(meta) = self.backend.stat(path).await
&& !meta.file_type.is_dir()
{
return Err(fs_errors::not_a_directory());
}
self.backend.read_dir(path).await
}
async fn exists(&self, path: &Path) -> Result<bool> {
self.backend.exists(path).await
}
async fn rename(&self, from: &Path, to: &Path) -> Result<()> {
self.backend.rename(from, to).await
}
async fn copy(&self, from: &Path, to: &Path) -> Result<()> {
if let Ok(meta) = self.backend.stat(from).await
&& meta.file_type.is_dir()
{
return Err(IoError::other("cannot copy directory").into());
}
self.backend.copy(from, to).await
}
async fn symlink(&self, target: &Path, link: &Path) -> Result<()> {
self.backend.symlink(target, link).await
}
async fn read_link(&self, path: &Path) -> Result<PathBuf> {
self.backend.read_link(path).await
}
async fn chmod(&self, path: &Path, mode: u32) -> Result<()> {
self.backend.chmod(path, mode).await
}
}
#[async_trait]
impl<B: FsBackend + 'static> FileSystemExt for PosixFs<B> {
fn usage(&self) -> FsUsage {
self.backend.usage()
}
fn limits(&self) -> FsLimits {
self.backend.limits()
}
}
impl<B: FsBackend + 'static> From<PosixFs<B>> for Arc<dyn FileSystem> {
fn from(fs: PosixFs<B>) -> Self {
Arc::new(fs)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fs::InMemoryFs;
use std::path::Path;
#[tokio::test]
async fn test_posix_write_to_directory_fails() {
let fs = InMemoryFs::new();
fs.mkdir(Path::new("/tmp/testdir"), false)
.await
.expect("mkdir should succeed");
let result = fs.write_file(Path::new("/tmp/testdir"), b"test").await;
assert!(result.is_err());
assert!(
result
.expect_err("write_file should fail")
.to_string()
.contains("directory")
);
}
#[tokio::test]
async fn test_posix_mkdir_on_file_fails() {
let fs = InMemoryFs::new();
fs.write_file(Path::new("/tmp/testfile"), b"test")
.await
.expect("write_file should succeed");
let result = fs.mkdir(Path::new("/tmp/testfile"), false).await;
assert!(result.is_err());
}
}