From 809861a38e29d13b0fe70aa156ad68a2cdf0c9d6 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sat, 23 Sep 2023 19:15:56 -0400 Subject: [PATCH] Load file by ID --- file-service/src/main.rs | 27 ++++++---- file-service/src/store/file.rs | 35 ++++++------ file-service/src/store/fileinfo.rs | 11 +++- file-service/src/store/mod.rs | 83 ++++++++++++++++++++--------- file-service/src/store/thumbnail.rs | 11 ++-- 5 files changed, 104 insertions(+), 63 deletions(-) diff --git a/file-service/src/main.rs b/file-service/src/main.rs index 22586f6..d8d5acc 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::{FileInfo, Store}; +pub use store::{File, FileId, FileInfo, Store}; /* fn is_admin(resource: &ResourceName, permissions: &Permissions) -> bool { @@ -229,23 +229,24 @@ fn script(_: &mut Request) -> IronResult { fn serve_file( info: FileInfo, - mut file: std::fs::File, + file: File, old_etags: Option, ) -> http::Result>> { - let mut content = Vec::new(); match old_etags { Some(old_etags) if old_etags != info.hash => warp::http::Response::builder() .header("content-type", info.file_type) .status(StatusCode::NOT_MODIFIED) - .body(content), - _ => { - let _ = file.read_to_end(&mut content); - warp::http::Response::builder() + .body(vec![]), + _ => match file.content() { + Ok(content) => warp::http::Response::builder() .header("content-type", info.file_type) .header("etag", info.hash) .status(StatusCode::OK) - .body(content) - } + .body(content), + Err(err) => warp::http::Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(vec![]), + }, } } @@ -359,7 +360,7 @@ pub async fn main() { move |id: String, old_etags: Option| match app .read() .unwrap() - .get_thumbnail(&id) + .get_thumbnail(FileId::from(id)) { Ok((info, file)) => serve_file(info, file, old_etags), Err(_err) => warp::http::Response::builder() @@ -373,7 +374,11 @@ pub async fn main() { .and(warp::header::optional::("if-none-match")) .map({ let app = app.clone(); - move |id: String, old_etags: Option| match app.read().unwrap().get_file(&id) { + move |id: String, old_etags: Option| match app + .read() + .unwrap() + .get_file(FileId::from(id)) + { Ok((info, file)) => serve_file(info, file, old_etags), Err(_err) => warp::http::Response::builder() .status(StatusCode::NOT_FOUND) diff --git a/file-service/src/store/file.rs b/file-service/src/store/file.rs index c9e969f..4abf8fc 100644 --- a/file-service/src/store/file.rs +++ b/file-service/src/store/file.rs @@ -27,19 +27,29 @@ impl File { let id = FileId::from(Uuid::new_v4().hyphenated().to_string()); let mut path = context.root(); - path.push(filename.clone()); - let path = PathResolver::try_from(path).map_err(|_| WriteFileError::InvalidPath)?; + let extension = PathBuf::from(filename) + .extension() + .and_then(|s| s.to_str().map(|s| s.to_owned())) + .ok_or(WriteFileError::InvalidPath)?; + path.push((*id).clone()); + let path = PathResolver { + base: context.root().clone(), + id: (*id).clone(), + extension: extension.clone(), + }; - let file_type = mime_guess::from_ext(&filename) + let file_type = mime_guess::from_ext(&extension) .first_or_text_plain() .essence_str() .to_owned(); let info = FileInfo { + id: id.clone(), size: 0, created: Utc::now(), file_type, hash: "".to_owned(), + extension, }; let mut md_file = std::fs::File::create(path.metadata_path())?; @@ -49,21 +59,12 @@ impl File { } pub fn load(resolver: PathResolver) -> Result { - /* + let info = FileInfo::load(resolver.metadata_path())?; Ok(Self { - id: FileId::from( - resolver - .file_path() - .file_stem() - .unwrap() - .to_string_lossy() - .to_owned(), - ), + id: info.id.clone(), path: resolver, - info: FileInfo::load(resolver.metadata_path())?, + info, }) - */ - unimplemented!() } pub fn set_content(&mut self, content: Vec) -> Result<(), WriteFileError> { @@ -74,6 +75,8 @@ impl File { let mut md_file = std::fs::File::create(self.path.metadata_path())?; md_file.write(&serde_json::to_vec(&self.info)?)?; + Thumbnail::open(self.path.file_path(), self.path.thumbnail_path())?; + Ok(()) } @@ -245,7 +248,7 @@ mod test { fn it_raises_an_error_when_file_not_found() { let resolver = PathResolver::try_from("fixtures/rawr.png").expect("a valid path"); match File::load(resolver) { - Err(ReadFileError::FileNotFound) => assert!(true), + Err(ReadFileError::FileNotFound(_)) => assert!(true), _ => assert!(false), } } diff --git a/file-service/src/store/fileinfo.rs b/file-service/src/store/fileinfo.rs index ab797b1..38e8917 100644 --- a/file-service/src/store/fileinfo.rs +++ b/file-service/src/store/fileinfo.rs @@ -1,3 +1,5 @@ +use crate::FileId; + use super::{ReadFileError, WriteFileError}; use chrono::prelude::*; use serde::{Deserialize, Serialize}; @@ -9,16 +11,19 @@ use std::{ #[derive(Clone, Debug, Serialize, Deserialize)] pub struct FileInfo { + pub id: FileId, pub size: usize, pub created: DateTime, pub file_type: String, pub hash: String, + pub extension: String, } impl FileInfo { pub fn load(path: PathBuf) -> Result { let mut content: Vec = Vec::new(); - let mut file = std::fs::File::open(path)?; + let mut file = + std::fs::File::open(path.clone()).map_err(|_| ReadFileError::FileNotFound(path))?; file.read_to_end(&mut content)?; let js = serde_json::from_slice(&content)?; @@ -96,7 +101,7 @@ impl FileInfo { #[cfg(test)] mod test { use super::*; - use crate::store::{utils::FileCleanup, PathResolver}; + use crate::store::{utils::FileCleanup, FileId, PathResolver}; use std::convert::TryFrom; #[test] @@ -108,10 +113,12 @@ mod test { let created = Utc::now(); let info = FileInfo { + id: FileId("temp-id".to_owned()), size: 23777, created, file_type: "image/png".to_owned(), hash: "abcdefg".to_owned(), + extension: "png".to_owned(), }; info.save(resolver.metadata_path()).unwrap(); diff --git a/file-service/src/store/mod.rs b/file-service/src/store/mod.rs index 1832879..8c9b050 100644 --- a/file-service/src/store/mod.rs +++ b/file-service/src/store/mod.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use std::{ convert::TryFrom, ops::Deref, @@ -39,7 +40,7 @@ pub enum WriteFileError { #[derive(Debug, Error)] pub enum ReadFileError { #[error("file not found")] - FileNotFound, + FileNotFound(PathBuf), #[error("path is not a file")] NotAFile, @@ -143,7 +144,7 @@ impl TryFrom<&Path> for PathResolver { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct FileId(String); impl From for FileId { @@ -197,7 +198,7 @@ impl Store { Ok(file) } - pub fn delete_file(&mut self, id: String) -> Result<(), WriteFileError> { + pub fn delete_file(&mut self, id: FileId) -> Result<(), WriteFileError> { /* let f = File::open(&id, &self.files_root)?; f.delete() @@ -205,36 +206,36 @@ impl Store { unimplemented!() } - pub fn get_metadata(&self, id: String) -> Result { - // FileInfo::open(&id, &self.files_root) - unimplemented!() + pub fn get_metadata(&self, id: FileId) -> Result { + let mut path = self.files_root.clone(); + path.push(PathBuf::from((*id).clone())); + path.set_extension("json"); + FileInfo::load(path) } - pub fn get_file(&self, id: &str) -> Result<(FileInfo, std::fs::File), ReadFileError> { - /* - let f = File::open(&id, &self.files_root)?; - let info = f.info(); - let stream = f.stream()?; - Ok((info, stream)) - */ - unimplemented!() + pub fn get_file(&self, id: FileId) -> Result<(FileInfo, File), ReadFileError> { + let info = self.get_metadata(id.clone())?; + + let resolver = PathResolver { + base: self.files_root.clone(), + id: (*id).clone(), + extension: info.extension.clone(), + }; + + let f = File::load(resolver)?; + Ok((info, f)) } - pub fn get_thumbnail(&self, id: &str) -> Result<(FileInfo, std::fs::File), ReadFileError> { - /* - let f = File::open(id, &self.files_root)?; - let stream = f.thumbnail().stream()?; - Ok((f.info(), stream)) - */ + pub fn get_thumbnail(&self, id: FileId) -> Result<(FileInfo, File), ReadFileError> { unimplemented!() } } #[cfg(test)] mod test { - use crate::store::utils::FileCleanup; - use super::*; + use crate::store::utils::FileCleanup; + use cool_asserts::assert_matches; use std::io::Read; #[test] @@ -264,17 +265,47 @@ mod test { 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/{}", *file_record.id))); + 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/{}.png.json", *file_record.id)).exists()); - assert!(PathBuf::from(format!("var/{}.png.tn", *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_metadata_for_file() {} + 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(); + + 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!(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() {} diff --git a/file-service/src/store/thumbnail.rs b/file-service/src/store/thumbnail.rs index 1b7c6ad..250c46c 100644 --- a/file-service/src/store/thumbnail.rs +++ b/file-service/src/store/thumbnail.rs @@ -43,14 +43,6 @@ impl Thumbnail { */ /* - fn thumbnail_path(id: &str, root: &Path) -> PathBuf { - let mut path = PathBuf::from(root); - path.push(".thumbnails"); - path.push(id.clone()); - path - } - */ - pub fn stream(&self) -> Result { std::fs::File::open(self.path.clone()).map_err(|err| { if err.kind() == std::io::ErrorKind::NotFound { @@ -60,10 +52,13 @@ impl Thumbnail { } }) } + */ + /* pub fn delete(self) -> Result<(), WriteFileError> { remove_file(self.path).map_err(WriteFileError::from) } + */ } #[cfg(test)]