use crate::ServerError;
use crate::config::Config;
use async_trait::async_trait;
use lb_rs::model::crypto::EncryptedDocument;
use lb_rs::model::file_metadata::DocumentHmac;
use std::collections::HashMap;
use std::fmt::Debug;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use tokio::fs::{File, remove_file};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use uuid::Uuid;
#[async_trait]
pub trait DocumentService: Send + Sync + Clone + 'static {
async fn insert<T: Debug>(
&self, id: &Uuid, hmac: &DocumentHmac, content: &EncryptedDocument,
) -> Result<(), ServerError<T>>;
async fn get<T: Debug>(
&self, id: &Uuid, hmac: &DocumentHmac,
) -> Result<EncryptedDocument, ServerError<T>>;
async fn maybe_get<T: Debug>(
&self, id: &Uuid, hmac: &DocumentHmac,
) -> Result<Option<EncryptedDocument>, ServerError<T>>;
async fn delete<T: Debug>(&self, id: &Uuid, hmac: &DocumentHmac) -> Result<(), ServerError<T>>;
fn exists(&self, id: &Uuid, hmac: &DocumentHmac) -> bool;
fn get_path(&self, id: &Uuid, hmac: &DocumentHmac) -> PathBuf;
}
#[derive(Clone)]
pub struct OnDiskDocuments {
config: Config,
}
impl From<&Config> for OnDiskDocuments {
fn from(value: &Config) -> Self {
Self { config: value.clone() }
}
}
#[async_trait]
impl DocumentService for OnDiskDocuments {
async fn insert<T: Debug>(
&self, id: &Uuid, hmac: &DocumentHmac, content: &EncryptedDocument,
) -> Result<(), ServerError<T>> {
let content = bincode::serialize(content)?;
let path = self.get_path(id, hmac);
let mut file = File::create(path.clone()).await?;
file.write_all(&content)
.await
.map_err(|err| internal!("{:?}", err))?;
file.flush().await.map_err(|err| internal!("{:?}", err))?;
Ok(())
}
async fn get<T: Debug>(
&self, id: &Uuid, hmac: &DocumentHmac,
) -> Result<EncryptedDocument, ServerError<T>> {
let path = self.get_path(id, hmac);
let mut file = File::open(path.clone()).await?;
let mut content = vec![];
file.read_to_end(&mut content).await?;
let content = bincode::deserialize(&content)?;
Ok(content)
}
async fn maybe_get<T: Debug>(
&self, id: &Uuid, hmac: &DocumentHmac,
) -> Result<Option<EncryptedDocument>, ServerError<T>> {
let path = self.get_path(id, hmac);
if !path.exists() {
return Ok(None);
}
Ok(Some(self.get(id, hmac).await?))
}
async fn delete<T: Debug>(&self, id: &Uuid, hmac: &DocumentHmac) -> Result<(), ServerError<T>> {
let path = self.get_path(id, hmac);
if path.exists() {
remove_file(path).await?;
}
Ok(())
}
fn exists(&self, id: &Uuid, hmac: &DocumentHmac) -> bool {
self.get_path(id, hmac).exists()
}
fn get_path(&self, id: &Uuid, hmac: &DocumentHmac) -> PathBuf {
let mut path = self.config.files.path.clone();
let hmac = base64::encode_config(hmac, base64::URL_SAFE);
path.push(format!("{id}-{hmac}"));
path
}
}
#[derive(Clone, Default)]
pub struct InMemDocuments {
pub docs: Arc<Mutex<HashMap<String, EncryptedDocument>>>,
}
#[async_trait]
impl DocumentService for InMemDocuments {
async fn insert<T: Debug>(
&self, id: &Uuid, hmac: &DocumentHmac, content: &EncryptedDocument,
) -> Result<(), ServerError<T>> {
let hmac = base64::encode_config(hmac, base64::URL_SAFE);
let key = format!("{id}-{hmac}");
self.docs.lock().unwrap().insert(key, content.clone());
Ok(())
}
async fn get<T: Debug>(
&self, id: &Uuid, hmac: &DocumentHmac,
) -> Result<EncryptedDocument, ServerError<T>> {
let hmac = base64::encode_config(hmac, base64::URL_SAFE);
let key = format!("{id}-{hmac}");
Ok(self.docs.lock().unwrap().get(&key).unwrap().clone())
}
async fn maybe_get<T: Debug>(
&self, id: &Uuid, hmac: &DocumentHmac,
) -> Result<Option<EncryptedDocument>, ServerError<T>> {
let hmac = base64::encode_config(hmac, base64::URL_SAFE);
let key = format!("{id}-{hmac}");
Ok(self.docs.lock().unwrap().get(&key).cloned())
}
fn exists(&self, id: &Uuid, hmac: &DocumentHmac) -> bool {
let hmac = base64::encode_config(hmac, base64::URL_SAFE);
let key = format!("{id}-{hmac}");
self.docs.lock().unwrap().contains_key(&key)
}
fn get_path(&self, _id: &Uuid, _hmac: &DocumentHmac) -> PathBuf {
unimplemented!()
}
async fn delete<T: Debug>(&self, id: &Uuid, hmac: &DocumentHmac) -> Result<(), ServerError<T>> {
let hmac = base64::encode_config(hmac, base64::URL_SAFE);
let key = format!("{id}-{hmac}");
self.docs.lock().unwrap().remove(&key);
Ok(())
}
}