use std::collections::HashMap;
use std::sync::Arc;
use bytes::Bytes;
use crate::storage::config::StorageConfig;
use crate::storage::error::{StorageConnectError, StorageError};
use crate::storage::path::StoragePath;
#[derive(Clone)]
pub struct Storage {
inner: Arc<StorageInner>,
}
struct StorageInner {
disks: HashMap<String, Disk>,
default_name: String,
}
#[derive(Clone)]
pub struct Disk {
pub(crate) operator: opendal::Operator,
}
impl Storage {
#[must_use]
pub fn builder() -> StorageBuilder {
StorageBuilder {
disks: Vec::new(),
default_name: None,
}
}
pub async fn connect(config: StorageConfig) -> Result<Storage, StorageConnectError> {
config.validate()?;
let operator = build_operator(&config).await?;
let mut disks = HashMap::new();
disks.insert("default".to_string(), Disk { operator });
Ok(Storage {
inner: Arc::new(StorageInner {
disks,
default_name: "default".to_string(),
}),
})
}
#[must_use]
pub fn disk(&self, name: &str) -> Disk {
self.try_disk(name)
.unwrap_or_else(|| panic!("storage disk `{name}` is not registered"))
}
#[must_use]
pub fn try_disk(&self, name: &str) -> Option<Disk> {
self.inner.disks.get(name).cloned()
}
#[must_use]
pub fn default_disk(&self) -> Disk {
self.disk(&self.inner.default_name)
}
#[must_use]
pub fn disk_names(&self) -> Vec<&str> {
self.inner.disks.keys().map(String::as_str).collect()
}
}
impl Disk {
#[must_use]
pub fn from_operator(operator: opendal::Operator) -> Self {
Self { operator }
}
#[must_use]
pub fn operator(&self) -> &opendal::Operator {
&self.operator
}
pub async fn put(&self, path: &StoragePath, data: &[u8]) -> Result<(), StorageError> {
self.operator
.write(path.as_str(), opendal::Buffer::from(data.to_vec()))
.await?;
Ok(())
}
pub async fn get(&self, path: &StoragePath) -> Result<Bytes, StorageError> {
let buffer = self.operator.read(path.as_str()).await?;
Ok(buffer.to_bytes())
}
pub async fn delete(&self, path: &StoragePath) -> Result<(), StorageError> {
self.operator.delete(path.as_str()).await?;
Ok(())
}
pub async fn exists(&self, path: &StoragePath) -> Result<bool, StorageError> {
let exists = self.operator.exists(path.as_str()).await?;
Ok(exists)
}
pub async fn stat(&self, path: &StoragePath) -> Result<opendal::Metadata, StorageError> {
let meta = self.operator.stat(path.as_str()).await?;
Ok(meta)
}
pub async fn list(&self, path: &StoragePath) -> Result<Vec<opendal::Entry>, StorageError> {
use futures::TryStreamExt;
let lister = self.operator.lister(path.as_str()).await?;
let mut entries = Vec::new();
let mut lister = lister;
while let Some(entry) = lister.try_next().await? {
entries.push(entry);
}
Ok(entries)
}
pub async fn reader(&self, path: &StoragePath) -> Result<opendal::Reader, StorageError> {
let reader = self.operator.reader(path.as_str()).await?;
Ok(reader)
}
pub async fn writer(&self, path: &StoragePath) -> Result<opendal::Writer, StorageError> {
let writer = self.operator.writer(path.as_str()).await?;
Ok(writer)
}
pub async fn copy(&self, from: &StoragePath, to: &StoragePath) -> Result<(), StorageError> {
self.operator.copy(from.as_str(), to.as_str()).await?;
Ok(())
}
pub async fn rename(&self, from: &StoragePath, to: &StoragePath) -> Result<(), StorageError> {
self.operator.rename(from.as_str(), to.as_str()).await?;
Ok(())
}
}
async fn build_operator(config: &StorageConfig) -> Result<opendal::Operator, opendal::Error> {
match config {
#[cfg(feature = "storage-fs")]
StorageConfig::Fs(fs) => {
let builder = fs.clone().into_builder();
Operator::new(builder)
}
#[cfg(feature = "storage-s3")]
StorageConfig::S3(s3) => {
opendal::install_default();
let builder = s3.clone().into_builder();
Operator::new(builder)
}
#[allow(unreachable_patterns)]
_ => unreachable!("no storage backend feature is enabled"),
}
}
#[cfg(any(feature = "storage-fs", feature = "storage-s3"))]
use opendal::Operator;
pub struct StorageBuilder {
disks: Vec<(String, StorageConfig)>,
default_name: Option<String>,
}
impl StorageBuilder {
#[must_use]
pub fn disk(mut self, name: impl Into<String>, config: StorageConfig) -> Self {
self.disks.push((name.into(), config));
self
}
#[must_use]
pub fn default_disk(mut self, name: impl Into<String>) -> Self {
self.default_name = Some(name.into());
self
}
pub async fn connect(self) -> Result<Storage, StorageConnectError> {
if self.disks.is_empty() {
return Err(StorageConnectError::Config {
source: crate::storage::error::StorageConfigError::EmptyRoot,
});
}
let default_name = self.default_name.unwrap_or_else(|| self.disks[0].0.clone());
let mut disks = HashMap::new();
for (name, config) in self.disks {
config.validate()?;
let operator = build_operator(&config).await?;
disks.insert(name, Disk { operator });
}
if !disks.contains_key(&default_name) {
return Err(StorageConnectError::Config {
source: crate::storage::error::StorageConfigError::EmptyRoot,
});
}
Ok(Storage {
inner: Arc::new(StorageInner {
disks,
default_name,
}),
})
}
}