161 lines
4.6 KiB
Rust
161 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"),
|
|
}),
|
|
},
|
|
}
|
|
}
|
|
}
|