use crate::prelude::*;
use beet_core::prelude::*;
use bytes::Bytes;
#[derive(Debug, Clone)]
pub struct FsBucketProvider {
root: AbsPathBuf,
}
impl FsBucketProvider {
pub fn new(root: impl Into<AbsPathBuf>) -> Self {
Self { root: root.into() }
}
fn resolve_path(&self, bucket_name: &str, path: &RoutePath) -> AbsPathBuf {
self.root
.join(bucket_name)
.join(path.to_string().trim_start_matches('/'))
}
}
impl<T: TableRow> TableProvider<T> for FsBucketProvider {
fn box_clone_table(&self) -> Box<dyn TableProvider<T>> {
Box::new(self.clone())
}
}
impl BucketProvider for FsBucketProvider {
fn box_clone(&self) -> Box<dyn BucketProvider> { Box::new(self.clone()) }
fn region(&self) -> Option<String> { None }
fn bucket_exists(
&self,
bucket_name: &str,
) -> SendBoxedFuture<Result<bool>> {
let path = self.root.join(bucket_name);
Box::pin(async move { fs_ext::exists_async(path).await?.xok() })
}
fn bucket_create(&self, bucket_name: &str) -> SendBoxedFuture<Result<()>> {
let path = self.root.join(bucket_name);
Box::pin(async move {
fs_ext::create_dir_all_async(path).await?;
Ok(())
})
}
fn bucket_remove(&self, bucket_name: &str) -> SendBoxedFuture<Result<()>> {
let path = self.root.join(bucket_name);
Box::pin(async move {
fs_ext::remove_async(path).await?;
Ok(())
})
}
fn insert(
&self,
bucket_name: &str,
path: &RoutePath,
body: Bytes,
) -> SendBoxedFuture<Result<()>> {
let path = self.resolve_path(bucket_name, path);
Box::pin(async move {
fs_ext::write_async(path, body).await?;
Ok(())
})
}
fn list(
&self,
bucket_name: &str,
) -> SendBoxedFuture<Result<Vec<RoutePath>>> {
let bucket_path = self.root.join(bucket_name);
Box::pin(async move {
ReadDir::files_recursive_async(&bucket_path)
.await?
.into_iter()
.map(|path| {
let path = path
.strip_prefix(&bucket_path)
.unwrap_or_else(|_| path.as_path());
RoutePath::new(path)
})
.collect::<Vec<_>>()
.xok()
})
}
fn exists(
&self,
bucket_name: &str,
path: &RoutePath,
) -> SendBoxedFuture<Result<bool>> {
let path = self.resolve_path(bucket_name, path);
Box::pin(async move { fs_ext::exists_async(path).await?.xok() })
}
fn get(
&self,
bucket_name: &str,
path: &RoutePath,
) -> SendBoxedFuture<Result<Bytes>> {
let path = self.resolve_path(bucket_name, path);
Box::pin(async move {
fs_ext::read_async(&path)
.await
.map_err(|_| HttpError::not_found())?
.xmap(Bytes::from)
.xok()
})
}
fn remove(
&self,
bucket_name: &str,
path: &RoutePath,
) -> SendBoxedFuture<Result<()>> {
let path = self.resolve_path(bucket_name, path);
Box::pin(async move { fs_ext::remove_async(path).await?.xok() })
}
fn public_url(
&self,
_bucket_name: &str,
_path: &RoutePath,
) -> SendBoxedFuture<Result<Option<String>>> {
Box::pin(async move { Ok(None) })
}
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod test {
use crate::prelude::*;
use beet_core::prelude::*;
#[beet_core::test]
async fn works() {
let dir = "target/tests/beet_net/test-bucket-001";
let provider =
FsBucketProvider::new(AbsPathBuf::new_workspace_rel(dir).unwrap());
bucket_test::run(provider).await;
}
}