use crate::prelude::*;
use beet_core::prelude::*;
use bytes::Bytes;
#[derive(Component)]
pub struct Bucket {
name: String,
provider: Box<dyn BucketProvider>,
}
impl Clone for Bucket {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
provider: self.provider.box_clone(),
}
}
}
impl Bucket {
pub fn new(provider: impl BucketProvider, name: impl Into<String>) -> Self {
Self {
name: name.into(),
provider: Box::new(provider),
}
}
pub fn item(&self, path: RoutePath) -> BucketItem {
BucketItem::new(self.clone(), path)
}
pub fn name(&self) -> &str { &self.name }
pub async fn bucket_create(&self) -> Result {
self.provider.bucket_create(&self.name).await
}
pub async fn bucket_try_create(&self) -> Result {
self.provider.bucket_try_create(&self.name).await
}
pub async fn bucket_exists(&self) -> Result<bool> {
self.provider.bucket_exists(&self.name).await
}
pub async fn bucket_remove(&self) -> Result {
self.provider.bucket_remove(&self.name).await
}
pub async fn insert(
&self,
path: &RoutePath,
body: impl Into<Bytes>,
) -> Result {
self.provider.insert(&self.name, path, body.into()).await
}
pub async fn try_insert(
&self,
path: &RoutePath,
body: impl Into<Bytes>,
) -> Result {
if self.exists(path).await? {
bevybail!("Object already exists: {}", path)
} else {
self.insert(path, body).await
}
}
pub async fn exists(&self, path: &RoutePath) -> Result<bool> {
self.provider.exists(&self.name, path).await
}
pub async fn list(&self) -> Result<Vec<RoutePath>> {
self.provider.list(&self.name).await
}
pub async fn get(&self, path: &RoutePath) -> Result<Bytes> {
self.provider.get(&self.name, path).await
}
pub async fn get_all(&self) -> Result<Vec<(RoutePath, Bytes)>> {
self.list()
.await?
.into_iter()
.map(async |path| {
let data = self.get(&path).await?;
Ok::<_, BevyError>((path, data))
})
.xmap(async_ext::try_join_all)
.await
}
pub async fn remove(&self, path: &RoutePath) -> Result {
self.provider.remove(&self.name, path).await
}
pub async fn public_url(&self, path: &RoutePath) -> Result<Option<String>> {
self.provider.public_url(&self.name, path).await
}
pub async fn region(&self) -> Option<String> { self.provider.region() }
}
pub trait BucketProvider: 'static + Send + Sync {
fn box_clone(&self) -> Box<dyn BucketProvider>;
fn region(&self) -> Option<String>;
fn bucket_exists(&self, bucket_name: &str)
-> SendBoxedFuture<Result<bool>>;
fn bucket_create(&self, bucket_name: &str) -> SendBoxedFuture<Result>;
fn bucket_remove(&self, bucket_name: &str) -> SendBoxedFuture<Result>;
fn bucket_try_create(&self, bucket_name: &str) -> SendBoxedFuture<Result> {
let exists_fut = self.bucket_exists(bucket_name);
let create_fut = self.bucket_create(bucket_name);
Box::pin(async move {
if exists_fut.await? {
Ok(())
} else {
create_fut.await
}
})
}
fn insert(
&self,
bucket_name: &str,
path: &RoutePath,
body: Bytes,
) -> SendBoxedFuture<Result>;
fn list(
&self,
bucket_name: &str,
) -> SendBoxedFuture<Result<Vec<RoutePath>>>;
fn get(
&self,
bucket_name: &str,
path: &RoutePath,
) -> SendBoxedFuture<Result<Bytes>>;
fn exists(
&self,
bucket_name: &str,
path: &RoutePath,
) -> SendBoxedFuture<Result<bool>>;
fn remove(
&self,
bucket_name: &str,
path: &RoutePath,
) -> SendBoxedFuture<Result>;
fn public_url(
&self,
bucket_name: &str,
path: &RoutePath,
) -> SendBoxedFuture<Result<Option<String>>>;
}
pub fn temp_bucket() -> Bucket { Bucket::new(InMemoryProvider::new(), "temp") }
pub fn local_bucket(name: impl Into<String>) -> Bucket {
#[cfg(target_arch = "wasm32")]
return Bucket::new(LocalStorageProvider::new(), name);
#[cfg(not(target_arch = "wasm32"))]
return Bucket::new(
FsBucketProvider::new(
AbsPathBuf::new_workspace_rel(".cache/buckets").unwrap(),
),
name,
);
}
#[allow(unused_variables)]
pub async fn s3_fs_selector(
fs_path: AbsPathBuf,
bucket_name: impl AsRef<str>,
access: ServiceAccess,
) -> Bucket {
let bucket_name = bucket_name.as_ref();
match access {
ServiceAccess::Local => {
debug!("Bucket Selector - FS: {fs_path}");
Bucket::new(FsBucketProvider::new(fs_path.clone()), "")
}
#[cfg(not(all(feature = "aws", not(target_arch = "wasm32"))))]
ServiceAccess::Remote => {
debug!("Bucket Selector - FS (no aws or wasm): {fs_path}");
Bucket::new(FsBucketProvider::new(fs_path.clone()), "")
}
#[cfg(all(feature = "aws", not(target_arch = "wasm32")))]
ServiceAccess::Remote => {
debug!("Bucket Selector - S3: {bucket_name}");
let provider = S3Provider::create().await;
Bucket::new(provider, bucket_name)
}
}
}
#[cfg(test)]
pub mod bucket_test {
use crate::prelude::*;
use beet_core::prelude::*;
pub async fn run(provider: impl BucketProvider) {
let bucket = Bucket::new(provider, "beet-test-bucket");
let path = RoutePath::from("/test_path");
let body = bytes::Bytes::from("test_body");
bucket.bucket_remove().await.ok();
bucket.bucket_exists().await.unwrap().xpect_false();
bucket.bucket_try_create().await.unwrap();
bucket.exists(&path).await.unwrap().xpect_false();
bucket.remove(&path).await.xpect_err();
bucket.insert(&path, body.clone()).await.unwrap();
bucket.bucket_exists().await.unwrap().xpect_true();
bucket.exists(&path).await.unwrap().xpect_true();
bucket.list().await.unwrap().xpect_eq(vec![path.clone()]);
bucket.get(&path).await.unwrap().xpect_eq(body.clone());
bucket.get(&path).await.unwrap().xpect_eq(body);
bucket.remove(&path).await.unwrap();
bucket.get(&path).await.xpect_err();
bucket.bucket_remove().await.unwrap();
bucket.bucket_exists().await.unwrap().xpect_false();
}
}