konobangu/apps/recorder/src/dal/client.rs

201 lines
5.3 KiB
Rust

use std::fmt;
use async_trait::async_trait;
use bytes::Bytes;
use loco_rs::app::{AppContext, Initializer};
use once_cell::sync::OnceCell;
use opendal::{layers::LoggingLayer, services::Fs, Buffer, Operator};
use quirks_path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use url::Url;
use uuid::Uuid;
use super::AppDalConfig;
use crate::{app::App, config::AppConfigExt};
// TODO: wait app-context-trait to integrate
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DalContentCategory {
Image,
}
impl AsRef<str> for DalContentCategory {
fn as_ref(&self) -> &str {
match self {
Self::Image => "image",
}
}
}
static APP_DAL_CLIENT: OnceCell<AppDalClient> = OnceCell::new();
pub enum DalStoredUrl {
RelativePath { path: String },
Absolute { url: Url },
}
impl AsRef<str> for DalStoredUrl {
fn as_ref(&self) -> &str {
match &self {
Self::Absolute { url } => url.as_str(),
Self::RelativePath { path } => path,
}
}
}
impl fmt::Display for DalStoredUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_ref())
}
}
#[derive(Debug, Clone)]
pub struct AppDalClient {
pub data_dir: String,
}
impl AppDalClient {
pub fn new(config: AppDalConfig) -> Self {
Self {
data_dir: App::get_working_root()
.join(config.data_dir.as_deref().unwrap_or("./data"))
.to_string(),
}
}
pub fn app_instance() -> &'static AppDalClient {
APP_DAL_CLIENT
.get()
.expect("AppDalClient is not initialized")
}
pub fn get_fs(&self) -> Fs {
Fs::default().root(&self.data_dir)
}
pub fn create_filename(extname: &str) -> String {
format!("{}{}", Uuid::new_v4(), extname)
}
pub async fn store_object(
&self,
content_category: DalContentCategory,
subscriber_pid: &str,
bucket: Option<&str>,
filename: &str,
data: Bytes,
) -> color_eyre::eyre::Result<DalStoredUrl> {
match content_category {
DalContentCategory::Image => {
let fullname = [
subscriber_pid,
content_category.as_ref(),
bucket.unwrap_or_default(),
filename,
]
.into_iter()
.map(Path::new)
.collect::<PathBuf>();
let fs_op = Operator::new(self.get_fs())?
.layer(LoggingLayer::default())
.finish();
if let Some(dirname) = fullname.parent() {
let dirname = dirname.join("/");
fs_op.create_dir(dirname.as_str()).await?;
}
fs_op.write(fullname.as_str(), data).await?;
Ok(DalStoredUrl::RelativePath {
path: fullname.to_string(),
})
}
}
}
pub async fn exists_object(
&self,
content_category: DalContentCategory,
subscriber_pid: &str,
bucket: Option<&str>,
filename: &str,
) -> color_eyre::eyre::Result<Option<DalStoredUrl>> {
match content_category {
DalContentCategory::Image => {
let fullname = [
subscriber_pid,
content_category.as_ref(),
bucket.unwrap_or_default(),
filename,
]
.into_iter()
.map(Path::new)
.collect::<PathBuf>();
let fs_op = Operator::new(self.get_fs())?
.layer(LoggingLayer::default())
.finish();
if fs_op.exists(fullname.as_str()).await? {
Ok(Some(DalStoredUrl::RelativePath {
path: fullname.to_string(),
}))
} else {
Ok(None)
}
}
}
}
pub async fn load_object(
&self,
content_category: DalContentCategory,
subscriber_pid: &str,
bucket: Option<&str>,
filename: &str,
) -> color_eyre::eyre::Result<Buffer> {
match content_category {
DalContentCategory::Image => {
let fullname = [
subscriber_pid,
content_category.as_ref(),
bucket.unwrap_or_default(),
filename,
]
.into_iter()
.map(Path::new)
.collect::<PathBuf>();
let fs_op = Operator::new(self.get_fs())?
.layer(LoggingLayer::default())
.finish();
let data = fs_op.read(fullname.as_str()).await?;
Ok(data)
}
}
}
}
pub struct AppDalInitalizer;
#[async_trait]
impl Initializer for AppDalInitalizer {
fn name(&self) -> String {
String::from("AppDalInitalizer")
}
async fn before_run(&self, app_context: &AppContext) -> loco_rs::Result<()> {
let config = &app_context.config;
let app_dal_conf = config.get_app_conf()?.dal;
APP_DAL_CLIENT.get_or_init(|| AppDalClient::new(app_dal_conf));
Ok(())
}
}