From 98a07ce03e6bbc6588fa8a867221ad02976307dd Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sat, 23 Sep 2023 20:20:35 -0400 Subject: [PATCH] Provide a unified interface for the File and Thumbnail --- file-service/src/main.rs | 10 +-- file-service/src/store/file.rs | 43 +++++---- file-service/src/store/fileinfo.rs | 2 +- file-service/src/store/mod.rs | 131 +++++++++++++++++----------- file-service/src/store/thumbnail.rs | 31 ++++++- 5 files changed, 135 insertions(+), 82 deletions(-) diff --git a/file-service/src/main.rs b/file-service/src/main.rs index d8d5acc..5c3575f 100644 --- a/file-service/src/main.rs +++ b/file-service/src/main.rs @@ -30,7 +30,7 @@ mod middleware; mod pages; mod store; -pub use store::{File, FileId, FileInfo, Store}; +pub use store::{File, FileId, FileInfo, HasContent, Store}; /* fn is_admin(resource: &ResourceName, permissions: &Permissions) -> bool { @@ -229,7 +229,7 @@ fn script(_: &mut Request) -> IronResult { fn serve_file( info: FileInfo, - file: File, + file: impl HasContent, old_etags: Option, ) -> http::Result>> { match old_etags { @@ -243,7 +243,7 @@ fn serve_file( .header("etag", info.hash) .status(StatusCode::OK) .body(content), - Err(err) => warp::http::Response::builder() + Err(_) => warp::http::Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .body(vec![]), }, @@ -360,7 +360,7 @@ pub async fn main() { move |id: String, old_etags: Option| match app .read() .unwrap() - .get_thumbnail(FileId::from(id)) + .get_thumbnail(&FileId::from(id)) { Ok((info, file)) => serve_file(info, file, old_etags), Err(_err) => warp::http::Response::builder() @@ -377,7 +377,7 @@ pub async fn main() { move |id: String, old_etags: Option| match app .read() .unwrap() - .get_file(FileId::from(id)) + .get_file(&FileId::from(id)) { Ok((info, file)) => serve_file(info, file, old_etags), Err(_err) => warp::http::Response::builder() diff --git a/file-service/src/store/file.rs b/file-service/src/store/file.rs index 4abf8fc..5449ec3 100644 --- a/file-service/src/store/file.rs +++ b/file-service/src/store/file.rs @@ -1,6 +1,6 @@ use super::{ - fileinfo::FileInfo, thumbnail::Thumbnail, FileId, FileRoot, PathResolver, ReadFileError, - WriteFileError, + fileinfo::FileInfo, thumbnail::Thumbnail, FileId, FileRoot, HasContent, PathResolver, + ReadFileError, WriteFileError, }; use chrono::prelude::*; use hex_string::HexString; @@ -80,10 +80,6 @@ impl File { Ok(()) } - pub fn content(&self) -> Result, ReadFileError> { - unimplemented!() - } - pub fn hash_content(&self) -> Result { let mut buf = Vec::new(); let mut file = std::fs::File::open(self.path.file_path())?; @@ -190,6 +186,17 @@ impl File { */ } +impl HasContent for File { + fn content(&self) -> Result, ReadFileError> { + let mut content: Vec = Vec::new(); + + let mut file = std::fs::File::open(self.path.file_path())?; + file.read_to_end(&mut content)?; + + Ok(content) + } +} + #[cfg(test)] mod test { use super::*; @@ -209,26 +216,19 @@ mod test { let _md = FileCleanup(PathBuf::from("fixtures/.metadata/rawr.png.json")); let _tn = FileCleanup(PathBuf::from("fixtures/.thumbnails/rawr.png")); - File::new( - "rawr.png".to_owned(), - FileContext(PathBuf::from("fixtures/")), - ) - .expect("to succeed"); + File::new("rawr.png".to_owned(), FileContext(PathBuf::from("var/"))).expect("to succeed"); } #[test] fn it_can_return_a_thumbnail() { - let f = File::new( - "rawr.png".to_owned(), - FileContext(PathBuf::from("fixtures/")), - ) - .expect("to succeed"); + let f = File::new("rawr.png".to_owned(), FileContext(PathBuf::from("var/"))) + .expect("to succeed"); /* assert_eq!( f.thumbnail(), Thumbnail { id: String::from("rawr.png"), - root: PathBuf::from("fixtures/"), + root: PathBuf::from("var/"), }, ); */ @@ -236,17 +236,14 @@ mod test { #[test] fn it_can_return_a_file_stream() { - let f = File::new( - "rawr.png".to_owned(), - FileContext(PathBuf::from("fixtures/")), - ) - .expect("to succeed"); + let f = File::new("rawr.png".to_owned(), FileContext(PathBuf::from("var/"))) + .expect("to succeed"); // f.stream().expect("to succeed"); } #[test] fn it_raises_an_error_when_file_not_found() { - let resolver = PathResolver::try_from("fixtures/rawr.png").expect("a valid path"); + let resolver = PathResolver::try_from("var/rawr.png").expect("a valid path"); match File::load(resolver) { Err(ReadFileError::FileNotFound(_)) => assert!(true), _ => assert!(false), diff --git a/file-service/src/store/fileinfo.rs b/file-service/src/store/fileinfo.rs index 38e8917..55223a4 100644 --- a/file-service/src/store/fileinfo.rs +++ b/file-service/src/store/fileinfo.rs @@ -106,7 +106,7 @@ mod test { #[test] fn it_saves_and_loads_metadata() { - let resolver = PathResolver::try_from("fixtures/1617654d-a588-4714-b4fa-e00ed0a8a607.png") + let resolver = PathResolver::try_from("var/1617654d-a588-4714-b4fa-e00ed0a8a607.png") .expect("a valid path"); let _cleanup = FileCleanup(resolver.metadata_path()); diff --git a/file-service/src/store/mod.rs b/file-service/src/store/mod.rs index 8c9b050..4d098e3 100644 --- a/file-service/src/store/mod.rs +++ b/file-service/src/store/mod.rs @@ -64,6 +64,10 @@ pub enum PathError { InvalidPath, } +pub trait HasContent { + fn content(&self) -> Result, ReadFileError>; +} + #[derive(Clone, Debug)] pub struct PathResolver { base: PathBuf, @@ -159,6 +163,19 @@ impl From<&str> for FileId { } } +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 { @@ -198,7 +215,7 @@ impl Store { Ok(file) } - pub fn delete_file(&mut self, id: FileId) -> Result<(), WriteFileError> { + pub fn delete_file(&mut self, id: &FileId) -> Result<(), WriteFileError> { /* let f = File::open(&id, &self.files_root)?; f.delete() @@ -206,19 +223,19 @@ impl Store { unimplemented!() } - pub fn get_metadata(&self, id: FileId) -> Result { + pub fn get_metadata(&self, id: &FileId) -> Result { let mut path = self.files_root.clone(); - path.push(PathBuf::from((*id).clone())); + path.push(PathBuf::from(id)); path.set_extension("json"); FileInfo::load(path) } - pub fn get_file(&self, id: FileId) -> Result<(FileInfo, File), ReadFileError> { - let info = self.get_metadata(id.clone())?; + pub fn get_file(&self, id: &FileId) -> Result<(FileInfo, File), ReadFileError> { + let info = self.get_metadata(id)?; let resolver = PathResolver { base: self.files_root.clone(), - id: (*id).clone(), + id: (**id).clone(), extension: info.extension.clone(), }; @@ -226,8 +243,17 @@ impl Store { Ok((info, f)) } - pub fn get_thumbnail(&self, id: FileId) -> Result<(FileInfo, File), ReadFileError> { - unimplemented!() + pub fn get_thumbnail(&self, id: &FileId) -> Result<(FileInfo, Thumbnail), ReadFileError> { + let info = self.get_metadata(id)?; + + let resolver = PathResolver { + base: self.files_root.clone(), + id: (**id).clone(), + extension: info.extension.clone(), + }; + + let f = Thumbnail::load(resolver.thumbnail_path())?; + Ok((info, f)) } } @@ -238,6 +264,24 @@ mod test { use cool_asserts::assert_matches; use std::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 paths() { let resolver = PathResolver::try_from("path/82420255-d3c8-4d90-a582-f94be588c70c.png") @@ -258,60 +302,49 @@ mod test { #[test] fn adds_files() { - let mut buf = Vec::new(); - let mut file = std::fs::File::open("fixtures/rawr.png").unwrap(); - file.read_to_end(&mut buf).unwrap(); + with_file(|store, id| { + let (_, file) = store.get_file(&id).expect("to retrieve the file"); - let mut store = Store::new(PathBuf::from("var/")); - let file_record = store.add_file("rawr.png".to_owned(), buf).unwrap(); + assert_eq!(file.content().map(|file| file.len()).unwrap(), 23777); - 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", *file_record.id))); - - store - .get_file(file_record.id.clone()) - .expect("to retrieve the file"); - - assert!(PathBuf::from(format!("var/{}.png", *file_record.id)).exists()); - assert!(PathBuf::from(format!("var/{}.json", *file_record.id)).exists()); - assert!(PathBuf::from(format!("var/{}.tn.png", *file_record.id)).exists()); + 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() { - let mut buf = Vec::new(); - let mut file = std::fs::File::open("fixtures/rawr.png").unwrap(); - file.read_to_end(&mut buf).unwrap(); + with_file(|store, id| { + let info = store.get_metadata(&id).expect("to retrieve the metadata"); - 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", *file_record.id))); - - let info = store - .get_metadata(file_record.id.clone()) - .expect("to retrieve the file"); - - 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()); + 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()); + }); }); - - assert!(PathBuf::from(format!("var/{}.png", *file_record.id)).exists()); - assert!(PathBuf::from(format!("var/{}.json", *file_record.id)).exists()); - assert!(PathBuf::from(format!("var/{}.tn.png", *file_record.id)).exists()); } #[test] - fn sets_up_thumbnail_for_file() {} + 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() {} + 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() {} diff --git a/file-service/src/store/thumbnail.rs b/file-service/src/store/thumbnail.rs index 250c46c..1c706cc 100644 --- a/file-service/src/store/thumbnail.rs +++ b/file-service/src/store/thumbnail.rs @@ -1,8 +1,10 @@ +use super::{HasContent, ReadFileError, WriteFileError}; use image::imageops::FilterType; -use std::fs::remove_file; -use std::path::{Path, PathBuf}; - -use super::{ReadFileError, WriteFileError}; +use std::{ + fs::remove_file, + io::Read, + path::{Path, PathBuf}, +}; #[derive(Clone, Debug, PartialEq)] pub struct Thumbnail { @@ -27,6 +29,16 @@ impl Thumbnail { Ok(s) } + pub fn load(path: PathBuf) -> Result { + let s = Thumbnail { path: path.clone() }; + + if !s.path.exists() { + return Err(ReadFileError::FileNotFound(path)); + } + + Ok(s) + } + /* pub fn from_path(path: &Path) -> Result { let id = path @@ -61,6 +73,17 @@ impl Thumbnail { */ } +impl HasContent for Thumbnail { + fn content(&self) -> Result, ReadFileError> { + let mut content: Vec = Vec::new(); + + let mut file = std::fs::File::open(&self.path)?; + file.read_to_end(&mut content)?; + + Ok(content) + } +} + #[cfg(test)] mod test { use super::*;