use std::future::Future;
use std::io::Result;
use std::path::{Path, PathBuf};
use std::pin::Pin;
#[cfg(test)]
pub(crate) fn block_on_test<F: Future>(f: F) -> F::Output {
futures_lite::future::block_on(f)
}
#[cfg(not(target_arch = "wasm32"))]
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[cfg(target_arch = "wasm32")]
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
#[cfg(not(target_arch = "wasm32"))]
pub trait AsyncFileSystem: Send + Sync {
fn read_to_string<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<String>>;
fn write_file<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>>;
fn create_new<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>>;
fn delete_file<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>>;
fn list_md_files<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>>;
fn exists<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool>;
fn create_dir_all<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>>;
fn is_dir<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool>;
fn move_file<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>>;
fn read_binary<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<Vec<u8>>> {
Box::pin(async move {
self.read_to_string(path).await.map(|s| s.into_bytes())
})
}
fn write_binary<'a>(
&'a self,
_path: &'a Path,
_content: &'a [u8],
) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"Binary write not supported",
))
})
}
fn list_files<'a>(&'a self, _dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
Box::pin(async move {
Ok(vec![])
})
}
fn list_md_files_recursive<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
Box::pin(async move {
let mut all_files = self.list_md_files(dir).await?;
if let Ok(entries) = self.list_files(dir).await {
for entry in entries {
if self.is_dir(&entry).await
&& let Ok(subdir_files) = self.list_md_files_recursive(&entry).await
{
all_files.extend(subdir_files);
}
}
}
Ok(all_files)
})
}
fn list_all_files_recursive<'a>(
&'a self,
dir: &'a Path,
) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
Box::pin(async move {
let mut all_entries = Vec::new();
if let Ok(entries) = self.list_files(dir).await {
for entry in entries {
all_entries.push(entry.clone());
if self.is_dir(&entry).await
&& let Ok(subdir_entries) = self.list_all_files_recursive(&entry).await
{
all_entries.extend(subdir_entries);
}
}
}
Ok(all_entries)
})
}
}
#[cfg(target_arch = "wasm32")]
pub trait AsyncFileSystem {
fn read_to_string<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<String>>;
fn write_file<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>>;
fn create_new<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>>;
fn delete_file<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>>;
fn list_md_files<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>>;
fn exists<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool>;
fn create_dir_all<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>>;
fn is_dir<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool>;
fn move_file<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>>;
fn read_binary<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<Vec<u8>>> {
Box::pin(async move {
self.read_to_string(path).await.map(|s| s.into_bytes())
})
}
fn write_binary<'a>(
&'a self,
_path: &'a Path,
_content: &'a [u8],
) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"Binary write not supported",
))
})
}
fn list_files<'a>(&'a self, _dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
Box::pin(async move {
Ok(vec![])
})
}
fn list_md_files_recursive<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
Box::pin(async move {
let mut all_files = self.list_md_files(dir).await?;
if let Ok(entries) = self.list_files(dir).await {
for entry in entries {
if self.is_dir(&entry).await {
if let Ok(subdir_files) = self.list_md_files_recursive(&entry).await {
all_files.extend(subdir_files);
}
}
}
}
Ok(all_files)
})
}
fn list_all_files_recursive<'a>(
&'a self,
dir: &'a Path,
) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
Box::pin(async move {
let mut all_entries = Vec::new();
if let Ok(entries) = self.list_files(dir).await {
for entry in entries {
all_entries.push(entry.clone());
if self.is_dir(&entry).await {
if let Ok(subdir_entries) = self.list_all_files_recursive(&entry).await {
all_entries.extend(subdir_entries);
}
}
}
}
Ok(all_entries)
})
}
}
use super::FileSystem;
#[derive(Clone)]
pub struct SyncToAsyncFs<F: FileSystem> {
inner: F,
}
impl<F: FileSystem> SyncToAsyncFs<F> {
pub fn new(fs: F) -> Self {
Self { inner: fs }
}
pub fn inner(&self) -> &F {
&self.inner
}
pub fn into_inner(self) -> F {
self.inner
}
}
#[cfg(not(target_arch = "wasm32"))]
impl<F: FileSystem + Send + Sync> AsyncFileSystem for SyncToAsyncFs<F> {
fn read_to_string<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<String>> {
Box::pin(async move { self.inner.read_to_string(path) })
}
fn write_file<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.write_file(path, content) })
}
fn create_new<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.create_new(path, content) })
}
fn delete_file<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.delete_file(path) })
}
fn list_md_files<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
Box::pin(async move { self.inner.list_md_files(dir) })
}
fn exists<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool> {
Box::pin(async move { self.inner.exists(path) })
}
fn create_dir_all<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.create_dir_all(path) })
}
fn is_dir<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool> {
Box::pin(async move { self.inner.is_dir(path) })
}
fn move_file<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.move_file(from, to) })
}
fn read_binary<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<Vec<u8>>> {
Box::pin(async move { self.inner.read_binary(path) })
}
fn write_binary<'a>(&'a self, path: &'a Path, content: &'a [u8]) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.write_binary(path, content) })
}
fn list_files<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
Box::pin(async move { self.inner.list_files(dir) })
}
}
#[cfg(target_arch = "wasm32")]
impl<F: FileSystem> AsyncFileSystem for SyncToAsyncFs<F> {
fn read_to_string<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<String>> {
Box::pin(async move { self.inner.read_to_string(path) })
}
fn write_file<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.write_file(path, content) })
}
fn create_new<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.create_new(path, content) })
}
fn delete_file<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.delete_file(path) })
}
fn list_md_files<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
Box::pin(async move { self.inner.list_md_files(dir) })
}
fn exists<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool> {
Box::pin(async move { self.inner.exists(path) })
}
fn create_dir_all<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.create_dir_all(path) })
}
fn is_dir<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool> {
Box::pin(async move { self.inner.is_dir(path) })
}
fn move_file<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.move_file(from, to) })
}
fn read_binary<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<Vec<u8>>> {
Box::pin(async move { self.inner.read_binary(path) })
}
fn write_binary<'a>(&'a self, path: &'a Path, content: &'a [u8]) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { self.inner.write_binary(path, content) })
}
fn list_files<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
Box::pin(async move { self.inner.list_files(dir) })
}
}
#[cfg(not(target_arch = "wasm32"))]
impl<T: AsyncFileSystem + ?Sized> AsyncFileSystem for &T {
fn read_to_string<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<String>> {
(*self).read_to_string(path)
}
fn write_file<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>> {
(*self).write_file(path, content)
}
fn create_new<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>> {
(*self).create_new(path, content)
}
fn delete_file<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>> {
(*self).delete_file(path)
}
fn list_md_files<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
(*self).list_md_files(dir)
}
fn exists<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool> {
(*self).exists(path)
}
fn create_dir_all<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>> {
(*self).create_dir_all(path)
}
fn is_dir<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool> {
(*self).is_dir(path)
}
fn move_file<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
(*self).move_file(from, to)
}
fn read_binary<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<Vec<u8>>> {
(*self).read_binary(path)
}
fn write_binary<'a>(&'a self, path: &'a Path, content: &'a [u8]) -> BoxFuture<'a, Result<()>> {
(*self).write_binary(path, content)
}
fn list_files<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
(*self).list_files(dir)
}
}
#[cfg(target_arch = "wasm32")]
impl<T: AsyncFileSystem + ?Sized> AsyncFileSystem for &T {
fn read_to_string<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<String>> {
(*self).read_to_string(path)
}
fn write_file<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>> {
(*self).write_file(path, content)
}
fn create_new<'a>(&'a self, path: &'a Path, content: &'a str) -> BoxFuture<'a, Result<()>> {
(*self).create_new(path, content)
}
fn delete_file<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>> {
(*self).delete_file(path)
}
fn list_md_files<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
(*self).list_md_files(dir)
}
fn exists<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool> {
(*self).exists(path)
}
fn create_dir_all<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<()>> {
(*self).create_dir_all(path)
}
fn is_dir<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, bool> {
(*self).is_dir(path)
}
fn move_file<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
(*self).move_file(from, to)
}
fn read_binary<'a>(&'a self, path: &'a Path) -> BoxFuture<'a, Result<Vec<u8>>> {
(*self).read_binary(path)
}
fn write_binary<'a>(&'a self, path: &'a Path, content: &'a [u8]) -> BoxFuture<'a, Result<()>> {
(*self).write_binary(path, content)
}
fn list_files<'a>(&'a self, dir: &'a Path) -> BoxFuture<'a, Result<Vec<PathBuf>>> {
(*self).list_files(dir)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fs::InMemoryFileSystem;
#[test]
fn test_sync_to_async_wrapper() {
let sync_fs = InMemoryFileSystem::new();
sync_fs.write_file(Path::new("test.md"), "# Hello").unwrap();
let async_fs = SyncToAsyncFs::new(sync_fs);
let content = futures_lite_test_block_on(async_fs.read_to_string(Path::new("test.md")));
assert_eq!(content.unwrap(), "# Hello");
let exists = futures_lite_test_block_on(async_fs.exists(Path::new("test.md")));
assert!(exists);
let not_exists = futures_lite_test_block_on(async_fs.exists(Path::new("nonexistent.md")));
assert!(!not_exists);
}
#[test]
fn test_async_write_and_read() {
let sync_fs = InMemoryFileSystem::new();
let async_fs = SyncToAsyncFs::new(sync_fs);
let write_result =
futures_lite_test_block_on(async_fs.write_file(Path::new("new.md"), "New content"));
assert!(write_result.is_ok());
let content = futures_lite_test_block_on(async_fs.read_to_string(Path::new("new.md")));
assert_eq!(content.unwrap(), "New content");
}
#[test]
fn test_async_create_new() {
let sync_fs = InMemoryFileSystem::new();
let async_fs = SyncToAsyncFs::new(sync_fs);
let result =
futures_lite_test_block_on(async_fs.create_new(Path::new("created.md"), "Created!"));
assert!(result.is_ok());
let result2 =
futures_lite_test_block_on(async_fs.create_new(Path::new("created.md"), "Again!"));
assert!(result2.is_err());
}
#[test]
fn test_async_directory_operations() {
let sync_fs = InMemoryFileSystem::new();
let async_fs = SyncToAsyncFs::new(sync_fs);
let result = futures_lite_test_block_on(async_fs.create_dir_all(Path::new("a/b/c")));
assert!(result.is_ok());
let is_dir = futures_lite_test_block_on(async_fs.is_dir(Path::new("a/b/c")));
assert!(is_dir);
let parent_is_dir = futures_lite_test_block_on(async_fs.is_dir(Path::new("a/b")));
assert!(parent_is_dir);
}
#[test]
fn test_inner_access() {
let sync_fs = InMemoryFileSystem::new();
sync_fs.write_file(Path::new("test.md"), "content").unwrap();
let async_fs = SyncToAsyncFs::new(sync_fs);
assert!(async_fs.inner().exists(Path::new("test.md")));
let recovered = async_fs.into_inner();
assert!(recovered.exists(Path::new("test.md")));
}
fn futures_lite_test_block_on<F: Future>(f: F) -> F::Output {
use std::pin::pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
const VTABLE: RawWakerVTable = RawWakerVTable::new(
|_| RawWaker::new(std::ptr::null(), &VTABLE), |_| {}, |_| {}, |_| {}, );
let raw_waker = RawWaker::new(std::ptr::null(), &VTABLE);
let waker = unsafe { Waker::from_raw(raw_waker) };
let mut cx = Context::from_waker(&waker);
let mut pinned = pin!(f);
loop {
match pinned.as_mut().poll(&mut cx) {
Poll::Ready(output) => return output,
Poll::Pending => {
std::hint::spin_loop();
}
}
}
}
}