monorepo/file-service/src/store/mod.rs

236 lines
5.8 KiB
Rust
Raw Normal View History

2023-09-23 23:15:56 +00:00
use serde::{Deserialize, Serialize};
use std::{
ops::Deref,
path::{Path, PathBuf},
};
use thiserror::Error;
2023-09-24 16:08:09 +00:00
mod filehandle;
mod fileinfo;
mod thumbnail;
pub mod utils;
2023-09-24 16:08:09 +00:00
pub use filehandle::FileHandle;
pub use fileinfo::FileInfo;
pub use thumbnail::Thumbnail;
#[derive(Debug, Error)]
pub enum WriteFileError {
#[error("root file path does not exist")]
RootNotFound,
#[error("permission denied")]
PermissionDenied,
2023-09-23 19:17:49 +00:00
#[error("invalid path")]
InvalidPath,
2023-09-24 16:08:09 +00:00
#[error("no metadata available")]
NoMetadata,
#[error("file could not be loaded")]
LoadError(#[from] ReadFileError),
2023-09-23 19:17:49 +00:00
#[error("image conversion failed")]
ImageError(#[from] image::ImageError),
#[error("JSON error")]
JSONError(#[from] serde_json::error::Error),
#[error("IO error")]
IOError(#[from] std::io::Error),
}
#[derive(Debug, Error)]
pub enum ReadFileError {
#[error("file not found")]
2023-09-23 23:15:56 +00:00
FileNotFound(PathBuf),
#[error("path is not a file")]
NotAFile,
#[error("permission denied")]
PermissionDenied,
2023-09-23 19:17:49 +00:00
#[error("invalid path")]
InvalidPath,
#[error("JSON error")]
JSONError(#[from] serde_json::error::Error),
#[error("IO error")]
IOError(#[from] std::io::Error),
}
2023-09-24 16:08:09 +00:00
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub struct FileId(String);
impl From<String> for FileId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for FileId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<FileId> for PathBuf {
fn from(s: FileId) -> Self {
Self::from(&s)
}
}
impl From<&FileId> for PathBuf {
fn from(s: &FileId) -> Self {
let FileId(s) = s;
Self::from(s)
}
}
impl Deref for FileId {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub trait FileRoot {
fn root(&self) -> PathBuf;
}
pub struct Context(PathBuf);
impl FileRoot for Context {
fn root(&self) -> PathBuf {
self.0.clone()
}
}
pub struct Store {
files_root: PathBuf,
}
impl Store {
pub fn new(files_root: PathBuf) -> Self {
Self { files_root }
}
2023-09-24 16:08:09 +00:00
pub fn list_files(&self) -> Result<Vec<FileId>, ReadFileError> {
unimplemented!()
}
2023-09-24 16:08:09 +00:00
pub fn add_file(
&mut self,
filename: String,
content: Vec<u8>,
) -> Result<FileHandle, WriteFileError> {
let mut file = FileHandle::new(filename, self.files_root.clone())?;
file.set_content(content)?;
Ok(file)
}
2023-09-24 16:08:09 +00:00
pub fn get_file(&self, id: &FileId) -> Result<FileHandle, ReadFileError> {
FileHandle::load(id, &self.files_root)
}
pub fn delete_file(&mut self, id: &FileId) -> Result<(), WriteFileError> {
2023-09-24 16:08:09 +00:00
let handle = FileHandle::load(id, &self.files_root)?;
handle.delete();
Ok(())
}
pub fn get_metadata(&self, id: &FileId) -> Result<FileInfo, ReadFileError> {
2023-09-23 23:15:56 +00:00
let mut path = self.files_root.clone();
path.push(PathBuf::from(id));
2023-09-23 23:15:56 +00:00
path.set_extension("json");
FileInfo::load(path)
}
}
2023-09-23 01:56:43 +00:00
#[cfg(test)]
mod test {
use super::*;
2023-09-23 23:15:56 +00:00
use crate::store::utils::FileCleanup;
use cool_asserts::assert_matches;
2023-09-24 16:08:09 +00:00
use std::{collections::HashSet, io::Read};
2023-09-23 01:56:43 +00:00
fn with_file<F>(test_fn: F)
where
F: FnOnce(Store, FileId),
{
let mut buf = Vec::new();
let mut file = std::fs::File::open("fixtures/rawr.png").unwrap();
file.read_to_end(&mut buf).unwrap();
let mut store = Store::new(PathBuf::from("var/"));
let file_record = store.add_file("rawr.png".to_owned(), buf).unwrap();
let _file = FileCleanup(PathBuf::from(format!("var/{}.png", *file_record.id)));
let _md = FileCleanup(PathBuf::from(format!("var/{}.json", *file_record.id)));
let _tn = FileCleanup(PathBuf::from(format!("var/{}.tn.png", *file_record.id)));
test_fn(store, file_record.id);
}
#[test]
fn adds_files() {
with_file(|store, id| {
2023-09-24 16:08:09 +00:00
let file = store.get_file(&id).expect("to retrieve the file");
assert_eq!(file.content().map(|file| file.len()).unwrap(), 23777);
2023-09-23 23:15:56 +00:00
assert!(PathBuf::from(format!("var/{}.png", *id)).exists());
assert!(PathBuf::from(format!("var/{}.json", *id)).exists());
assert!(PathBuf::from(format!("var/{}.tn.png", *id)).exists());
});
}
#[test]
2023-09-23 23:15:56 +00:00
fn sets_up_metadata_for_file() {
with_file(|store, id| {
let info = store.get_metadata(&id).expect("to retrieve the metadata");
assert_matches!(info, FileInfo { size, file_type, hash, extension, .. } => {
assert_eq!(size, 23777);
assert_eq!(file_type, "image/png");
assert_eq!(hash, "".to_owned());
assert_eq!(extension, "png".to_owned());
});
2023-09-23 23:15:56 +00:00
});
}
2023-09-24 16:08:09 +00:00
/*
#[test]
fn sets_up_thumbnail_for_file() {
with_file(|store, id| {
let (_, thumbnail) = store.get_thumbnail(&id).expect("to retrieve the thumbnail");
assert_eq!(thumbnail.content().map(|file| file.len()).unwrap(), 48869);
});
}
2023-09-24 16:08:09 +00:00
*/
#[test]
fn deletes_associated_files() {
with_file(|mut store, id| {
store.delete_file(&id).expect("file to be deleted");
assert!(!PathBuf::from(format!("var/{}.png", *id)).exists());
assert!(!PathBuf::from(format!("var/{}.json", *id)).exists());
assert!(!PathBuf::from(format!("var/{}.tn.png", *id)).exists());
});
}
#[test]
2023-09-24 16:08:09 +00:00
fn lists_files_in_the_db() {
with_file(|store, id| {
let resolvers = store.list_files().expect("file listing to succeed");
let ids = resolvers.into_iter().collect::<HashSet<FileId>>();
assert_eq!(ids.len(), 1);
});
}
2023-09-23 01:56:43 +00:00
}