use std::path::{Path, PathBuf};
use async_trait::async_trait;
use crate::error::Result;
#[derive(Debug, Clone)]
pub struct FsDirEntry {
pub name: String,
pub path: PathBuf,
pub is_dir: bool,
}
#[async_trait]
pub trait Fs: Send + Sync {
async fn read_to_string(&self, path: &Path) -> Result<String>;
async fn read_dir(&self, path: &Path) -> Result<Vec<FsDirEntry>>;
async fn is_file(&self, path: &Path) -> bool;
async fn is_dir(&self, path: &Path) -> bool;
async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
}
pub async fn exists(fs: &dyn Fs, path: &Path) -> bool {
fs.is_file(path).await || fs.is_dir(path).await
}
pub async fn walk_dir(fs: &dyn Fs, root: &Path) -> Result<Vec<PathBuf>> {
let mut result = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
if !fs.is_dir(&dir).await {
continue;
}
let entries = fs.read_dir(&dir).await?;
for entry in entries {
if entry.is_dir {
stack.push(entry.path.clone());
} else {
result.push(entry.path);
}
}
}
Ok(result)
}
#[cfg(feature = "tokio")]
#[derive(Debug, Clone, Copy, Default)]
pub struct OsFs;
#[cfg(feature = "tokio")]
#[async_trait]
impl Fs for OsFs {
async fn read_to_string(&self, path: &Path) -> Result<String> {
Ok(tokio::fs::read_to_string(path).await?)
}
async fn read_dir(&self, path: &Path) -> Result<Vec<FsDirEntry>> {
let mut entries = Vec::new();
let mut rd = tokio::fs::read_dir(path).await?;
while let Some(entry) = rd.next_entry().await? {
let metadata = entry.metadata().await?;
entries.push(FsDirEntry {
name: entry.file_name().to_string_lossy().into_owned(),
path: entry.path(),
is_dir: metadata.is_dir(),
});
}
Ok(entries)
}
async fn is_file(&self, path: &Path) -> bool {
tokio::fs::metadata(path)
.await
.map(|m| m.is_file())
.unwrap_or(false)
}
async fn is_dir(&self, path: &Path) -> bool {
tokio::fs::metadata(path)
.await
.map(|m| m.is_dir())
.unwrap_or(false)
}
async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
Ok(tokio::fs::canonicalize(path).await?)
}
}