use async_trait::async_trait;
use std::path::PathBuf;
use thiserror::Error;
use tokio::fs::File;
use tokio::io::{AsyncRead, AsyncWriteExt};
#[derive(Debug, Clone, Error)]
pub enum StorageError {
#[error("Not found: {0}")]
NotFound(String),
#[error("IO error: {0}")]
IoError(String),
#[error("Permission denied: {0}")]
PermissionDenied(String),
#[error("Invalid path: {0}")]
InvalidPath(String),
}
pub struct StorageWriter {
file: File,
location: String,
}
impl StorageWriter {
pub(crate) fn new(file: File, location: String) -> Self {
Self { file, location }
}
pub async fn write_chunk(&mut self, chunk: &[u8]) -> Result<(), StorageError> {
self.file
.write_all(chunk)
.await
.map_err(|e| StorageError::IoError(format!("Failed to write chunk: {e}")))
}
pub async fn finish(mut self) -> Result<String, StorageError> {
self.file
.flush()
.await
.map_err(|e| StorageError::IoError(format!("Failed to flush file: {e}")))?;
Ok(self.location)
}
}
#[async_trait]
pub trait StorageTrait: Send + Sync {
async fn store(&self, data: &[u8], file_name: &str) -> Result<String, StorageError>;
async fn store_stream_dyn(
&self,
reader: &mut (dyn AsyncRead + Unpin + Send),
file_name: &str,
) -> Result<String, StorageError>;
async fn create_writer(&self, file_name: &str) -> Result<StorageWriter, StorageError>;
async fn retrieve(&self, location: &str) -> Result<Vec<u8>, StorageError>;
async fn exists(&self, location: &str) -> Result<bool, StorageError>;
async fn delete(&self, location: &str) -> Result<(), StorageError>;
fn get_full_path(&self, location: &str) -> PathBuf;
fn base_path(&self) -> &str;
async fn initialize(&self) -> Result<(), StorageError>;
async fn remove_all(&self) -> Result<(), StorageError>;
}
#[async_trait]
pub trait StorageExt: StorageTrait {
async fn store_stream<R: AsyncRead + Unpin + Send>(
&self,
reader: &mut R,
file_name: &str,
) -> Result<String, StorageError> {
self.store_stream_dyn(reader, file_name).await
}
}
impl<T: StorageTrait + ?Sized> StorageExt for T {}