hyaenidae/profiles/src/store/file.rs
2021-01-04 11:34:31 -06:00

162 lines
4.6 KiB
Rust

use super::{File, FileSource, PictRsFile, StoreError, Undo};
use chrono::{DateTime, Utc};
use sled::{Db, Transactional, Tree};
use std::io::Cursor;
use uuid::Uuid;
#[derive(Clone, Debug)]
pub struct Store {
file_tree: Tree,
created_tree: Tree,
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
struct StoredPictRsFile<'a> {
key: &'a str,
token: &'a str,
width: usize,
height: usize,
media_type: &'a str,
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
enum StoredFileSource<'a> {
#[serde(borrow)]
PictRs(StoredPictRsFile<'a>),
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct StoredFile<'a> {
id: Uuid,
#[serde(borrow)]
source: StoredFileSource<'a>,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
}
impl Store {
pub fn build(db: &Db) -> Result<Self, sled::Error> {
Ok(Store {
file_tree: db.open_tree("profiles/files")?,
created_tree: db.open_tree("profiles/files/created")?,
})
}
pub fn create(&self, source: &FileSource) -> Result<File, StoreError> {
let mut id;
let mut stored_file;
let now = Utc::now().into();
let mut stored_file_vec = vec![];
let stored_file_source = match source {
FileSource::PictRs(pict_rs_source) => StoredFileSource::PictRs(StoredPictRsFile {
key: &pict_rs_source.key,
token: &pict_rs_source.token,
width: pict_rs_source.width,
height: pict_rs_source.height,
media_type: pict_rs_source.media_type.essence_str(),
}),
};
while {
stored_file_vec.clear();
let writer = Cursor::new(&mut stored_file_vec);
id = Uuid::new_v4();
stored_file = StoredFile {
id,
source: stored_file_source.clone(),
created_at: now,
updated_at: now,
};
serde_json::to_writer(writer, &stored_file)?;
self.file_tree
.compare_and_swap(
id_file_key(id).as_bytes(),
None as Option<&[u8]>,
Some(stored_file_vec.as_slice()),
)?
.is_err()
} {}
if let Err(e) = self.created_tree.insert(
created_file_key(now, id).as_bytes(),
id.to_string().as_bytes(),
) {
self.file_tree.remove(id_file_key(id).as_bytes())?;
return Err(e.into());
}
Ok(stored_file.into())
}
pub fn by_id(&self, id: Uuid) -> Result<Option<File>, StoreError> {
let stored_file_ivec = match self.file_tree.get(id_file_key(id).as_bytes())? {
Some(ivec) => ivec,
None => return Ok(None),
};
let stored_file: StoredFile = serde_json::from_slice(&stored_file_ivec)?;
Ok(Some(stored_file.into()))
}
pub fn delete(&self, file_id: Uuid) -> Result<Option<Undo<File>>, StoreError> {
let stored_file_ivec = match self.file_tree.get(id_file_key(file_id))? {
Some(ivec) => ivec,
None => return Ok(None),
};
let stored_file: StoredFile = serde_json::from_slice(&stored_file_ivec)?;
let created_at = stored_file.created_at;
let file = stored_file.into();
&[&self.file_tree, &self.created_tree].transaction(move |trees| {
let file_tree = &trees[0];
let created_tree = &trees[1];
created_tree.remove(created_file_key(created_at, file_id).as_bytes())?;
file_tree.remove(id_file_key(file_id).as_bytes())?;
Ok(())
})?;
Ok(Some(Undo(file)))
}
}
// Used to map id -> File
fn id_file_key(id: Uuid) -> String {
format!("/file/{}/data", id)
}
// Used to map created_at -> id
fn created_file_key(created_at: DateTime<Utc>, id: Uuid) -> String {
format!("/created/{}/file/{}", created_at.to_rfc3339(), id)
}
impl<'a> From<StoredFile<'a>> for File {
fn from(sf: StoredFile<'a>) -> Self {
match sf.source {
StoredFileSource::PictRs(StoredPictRsFile {
key,
token,
width,
height,
media_type,
}) => File {
id: sf.id,
source: FileSource::PictRs(PictRsFile {
key: key.to_owned(),
token: token.to_owned(),
width,
height,
media_type: media_type.parse::<mime::Mime>().expect("Mime is valid"),
}),
},
}
}
}