use std::sync::Arc;
use async_trait::async_trait;
use crate::error::Result;
use crate::runtime::MaybeSendSync;
#[cfg(feature = "native")]
pub mod native;
#[cfg(feature = "native")]
pub use native::NativeFilesystem;
#[cfg(target_arch = "wasm32")]
pub mod opfs;
#[cfg(target_arch = "wasm32")]
pub use opfs::OpfsFilesystem;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryKind {
File,
Directory,
Symlink,
Other,
}
impl EntryKind {
pub fn as_str(&self) -> &'static str {
match self {
EntryKind::File => "file",
EntryKind::Directory => "directory",
EntryKind::Symlink => "symlink",
EntryKind::Other => "other",
}
}
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub name: String,
pub kind: EntryKind,
pub size: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct WalkEntry {
pub path: String,
pub kind: EntryKind,
pub size: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct Metadata {
pub kind: EntryKind,
pub size: u64,
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait Filesystem: MaybeSendSync + std::fmt::Debug {
async fn read(&self, path: &str) -> Result<Vec<u8>>;
async fn write_atomic(&self, path: &str, bytes: &[u8]) -> Result<()>;
async fn metadata(&self, path: &str) -> Result<Option<Metadata>>;
async fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>>;
async fn walk(&self, path: &str, max_depth: Option<usize>) -> Result<Vec<WalkEntry>>;
async fn delete(&self, path: &str) -> Result<()>;
async fn rename(&self, from: &str, to: &str) -> Result<()> {
let bytes = self.read(from).await?;
self.write_atomic(to, &bytes).await?;
self.delete(from).await?;
Ok(())
}
}
pub type SharedFilesystem = Arc<dyn Filesystem>;
pub(crate) fn file_name(p: &str) -> &str {
match p.rfind(['/', '\\']) {
Some(i) => &p[i + 1..],
None => p,
}
}
#[cfg(test)]
mod tests {
use super::file_name;
#[test]
fn file_name_no_separator() {
assert_eq!(file_name("foo.rs"), "foo.rs");
}
#[test]
fn file_name_unix_separator() {
assert_eq!(file_name("a/b/c.rs"), "c.rs");
}
#[test]
fn file_name_windows_separator() {
assert_eq!(file_name(r"a\b\c.rs"), "c.rs");
}
#[test]
fn file_name_mixed_separators() {
assert_eq!(file_name(r"C:\proj/sub\file.rs"), "file.rs");
}
#[test]
fn file_name_empty_string() {
assert_eq!(file_name(""), "");
}
#[test]
fn file_name_trailing_separator_returns_empty() {
assert_eq!(file_name("a/b/"), "");
}
}