use serde::{Deserialize, Serialize}; use std::{ ops::Deref, path::{Path, PathBuf}, }; use thiserror::Error; mod filehandle; mod fileinfo; mod thumbnail; pub mod utils; 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, #[error("invalid path")] InvalidPath, #[error("no metadata available")] NoMetadata, #[error("file could not be loaded")] LoadError(#[from] ReadFileError), #[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")] FileNotFound(PathBuf), #[error("path is not a file")] NotAFile, #[error("permission denied")] PermissionDenied, #[error("invalid path")] InvalidPath, #[error("JSON error")] JSONError(#[from] serde_json::error::Error), #[error("IO error")] IOError(#[from] std::io::Error), } #[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] pub struct FileId(String); impl From 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 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 } } pub fn list_files(&self) -> Result, ReadFileError> { unimplemented!() } pub fn add_file( &mut self, filename: String, content: Vec, ) -> Result { let mut file = FileHandle::new(filename, self.files_root.clone())?; file.set_content(content)?; Ok(file) } pub fn get_file(&self, id: &FileId) -> Result { FileHandle::load(id, &self.files_root) } pub fn delete_file(&mut self, id: &FileId) -> Result<(), WriteFileError> { let handle = FileHandle::load(id, &self.files_root)?; handle.delete(); Ok(()) } pub fn get_metadata(&self, id: &FileId) -> Result { let mut path = self.files_root.clone(); path.push(PathBuf::from(id)); path.set_extension("json"); FileInfo::load(path) } } #[cfg(test)] mod test { use super::*; use crate::store::utils::FileCleanup; use cool_asserts::assert_matches; use std::{collections::HashSet, io::Read}; fn with_file(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| { let file = store.get_file(&id).expect("to retrieve the file"); assert_eq!(file.content().map(|file| file.len()).unwrap(), 23777); 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] 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()); }); }); } /* #[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); }); } */ #[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] 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::>(); assert_eq!(ids.len(), 1); }); } }