diff --git a/file-service/src/store/file.rs b/file-service/src/store/file.rs index b81d56b..c9e969f 100644 --- a/file-service/src/store/file.rs +++ b/file-service/src/store/file.rs @@ -6,6 +6,7 @@ use chrono::prelude::*; use hex_string::HexString; use sha2::{Digest, Sha256}; use std::{ + convert::TryFrom, io::{Read, Write}, path::{Path, PathBuf}, }; @@ -26,8 +27,8 @@ impl File { let id = FileId::from(Uuid::new_v4().hyphenated().to_string()); let mut path = context.root(); - path.push((*id).to_owned()); - let path = PathResolver(path); + path.push(filename.clone()); + let path = PathResolver::try_from(path).map_err(|_| WriteFileError::InvalidPath)?; let file_type = mime_guess::from_ext(&filename) .first_or_text_plain() @@ -190,7 +191,7 @@ impl File { mod test { use super::*; use crate::store::utils::FileCleanup; - use std::path::PathBuf; + use std::{convert::TryFrom, path::PathBuf}; struct FileContext(PathBuf); @@ -242,7 +243,7 @@ mod test { #[test] fn it_raises_an_error_when_file_not_found() { - let resolver = PathResolver(PathBuf::from("fixtures/rawr.png")); + let resolver = PathResolver::try_from("fixtures/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 ad8f778..ab797b1 100644 --- a/file-service/src/store/fileinfo.rs +++ b/file-service/src/store/fileinfo.rs @@ -1,10 +1,11 @@ +use super::{ReadFileError, WriteFileError}; use chrono::prelude::*; use serde::{Deserialize, Serialize}; use serde_json; -use std::io::{Read, Write}; -use std::path::PathBuf; - -use super::{ReadFileError, WriteFileError}; +use std::{ + io::{Read, Write}, + path::PathBuf, +}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct FileInfo { @@ -95,12 +96,13 @@ impl FileInfo { #[cfg(test)] mod test { use super::*; - use crate::store::utils::FileCleanup; - use crate::store::PathResolver; + use crate::store::{utils::FileCleanup, PathResolver}; + use std::convert::TryFrom; #[test] fn it_saves_and_loads_metadata() { - let resolver = PathResolver::from("fixtures/1617654d-a588-4714-b4fa-e00ed0a8a607"); + let resolver = PathResolver::try_from("fixtures/1617654d-a588-4714-b4fa-e00ed0a8a607.png") + .expect("a valid path"); let _cleanup = FileCleanup(resolver.metadata_path()); let created = Utc::now(); diff --git a/file-service/src/store/mod.rs b/file-service/src/store/mod.rs index 1e732a3..1832879 100644 --- a/file-service/src/store/mod.rs +++ b/file-service/src/store/mod.rs @@ -1,4 +1,5 @@ use std::{ + convert::TryFrom, ops::Deref, path::{Path, PathBuf}, }; @@ -22,6 +23,12 @@ pub enum WriteFileError { #[error("permission denied")] PermissionDenied, + #[error("invalid path")] + InvalidPath, + + #[error("image conversion failed")] + ImageError(#[from] image::ImageError), + #[error("JSON error")] JSONError(#[from] serde_json::error::Error), @@ -40,6 +47,9 @@ pub enum ReadFileError { #[error("permission denied")] PermissionDenied, + #[error("invalid path")] + InvalidPath, + #[error("JSON error")] JSONError(#[from] serde_json::error::Error), @@ -47,36 +57,89 @@ pub enum ReadFileError { IOError(#[from] std::io::Error), } +#[derive(Debug, Error)] +pub enum PathError { + #[error("path cannot be derived from input")] + InvalidPath, +} + #[derive(Clone, Debug)] -pub struct PathResolver(pub PathBuf); +pub struct PathResolver { + base: PathBuf, + id: String, + extension: String, +} impl PathResolver { + fn new(base: &Path, id: &str, extension: &str) -> Self { + Self { + base: base.to_owned(), + id: id.to_owned(), + extension: extension.to_owned(), + } + } + fn file_path(&self) -> PathBuf { - self.0.clone() + let mut path = self.base.clone(); + path.push(self.id.clone()); + path.set_extension(self.extension.clone()); + path } fn metadata_path(&self) -> PathBuf { - let mut path = self.0.clone(); + let mut path = self.base.clone(); + path.push(self.id.clone()); path.set_extension("json"); path } fn thumbnail_path(&self) -> PathBuf { - let mut path = self.0.clone(); - path.set_extension("tn"); + let mut path = self.base.clone(); + path.push(self.id.clone()); + path.set_extension(format!("tn.{}", self.extension)); path } } -impl From for PathResolver { - fn from(s: String) -> Self { - Self(PathBuf::from(s)) +impl TryFrom for PathResolver { + type Error = PathError; + fn try_from(s: String) -> Result { + PathResolver::try_from(s.as_str()) } } -impl From<&str> for PathResolver { - fn from(s: &str) -> Self { - Self(PathBuf::from(s.to_owned())) +impl TryFrom<&str> for PathResolver { + type Error = PathError; + fn try_from(s: &str) -> Result { + let path = Path::new(s); + PathResolver::try_from(Path::new(s)) + } +} + +impl TryFrom for PathResolver { + type Error = PathError; + fn try_from(path: PathBuf) -> Result { + PathResolver::try_from(path.as_path()) + } +} + +impl TryFrom<&Path> for PathResolver { + type Error = PathError; + fn try_from(path: &Path) -> Result { + Ok(Self { + base: path + .parent() + .map(|s| s.to_owned()) + .ok_or(PathError::InvalidPath)?, + id: path + .file_stem() + .and_then(|s| s.to_str().map(|s| s.to_owned())) + .ok_or(PathError::InvalidPath)?, + extension: path + .extension() + .and_then(|s| s.to_str().map(|s| s.to_owned())) + .ok_or(PathError::InvalidPath)?, + }) } } @@ -176,10 +239,11 @@ mod test { #[test] fn paths() { - let resolver = PathResolver::from("path/82420255-d3c8-4d90-a582-f94be588c70c"); + let resolver = PathResolver::try_from("path/82420255-d3c8-4d90-a582-f94be588c70c.png") + .expect("to have a valid path"); assert_eq!( resolver.file_path(), - PathBuf::from("path/82420255-d3c8-4d90-a582-f94be588c70c") + PathBuf::from("path/82420255-d3c8-4d90-a582-f94be588c70c.png") ); assert_eq!( resolver.metadata_path(), @@ -187,7 +251,7 @@ mod test { ); assert_eq!( resolver.thumbnail_path(), - PathBuf::from("path/82420255-d3c8-4d90-a582-f94be588c70c.tn") + PathBuf::from("path/82420255-d3c8-4d90-a582-f94be588c70c.tn.png") ); } @@ -204,9 +268,9 @@ mod test { let _md = FileCleanup(PathBuf::from(format!("var/{}.json", *file_record.id))); let _tn = FileCleanup(PathBuf::from(format!("var/{}.tn", *file_record.id))); - assert!(PathBuf::from(format!("var/{}", *file_record.id)).exists()); - assert!(PathBuf::from(format!("var/{}.json", *file_record.id)).exists()); - assert!(PathBuf::from(format!("var/{}.tn", *file_record.id)).exists()); + 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()); } #[test] diff --git a/file-service/src/store/thumbnail.rs b/file-service/src/store/thumbnail.rs index 99ddd4d..1b7c6ad 100644 --- a/file-service/src/store/thumbnail.rs +++ b/file-service/src/store/thumbnail.rs @@ -6,31 +6,25 @@ use super::{ReadFileError, WriteFileError}; #[derive(Clone, Debug, PartialEq)] pub struct Thumbnail { - pub id: String, - pub root: PathBuf, + pub path: PathBuf, } impl Thumbnail { - pub fn open(id: &str, root: &Path) -> Result { - /* - let mut source_path = PathBuf::from(root); - source_path.push(id); - - let self_ = Thumbnail { - id: String::from(id), - root: PathBuf::from(root), + pub fn open( + origin_path: PathBuf, + thumbnail_path: PathBuf, + ) -> Result { + let s = Thumbnail { + path: PathBuf::from(thumbnail_path), }; - let thumbnail_path = Thumbnail::thumbnail_path(id, root); - if !thumbnail_path.exists() { - let img = image::open(source_path)?; + if !s.path.exists() { + let img = image::open(&origin_path)?; let tn = img.resize(640, 640, FilterType::Nearest); - tn.save(thumbnail_path)?; + tn.save(&s.path)?; } - Ok(self_) - */ - unimplemented!() + Ok(s) } /* @@ -40,7 +34,7 @@ impl Thumbnail { .map(|s| String::from(s.to_string_lossy())) .ok_or(ReadFileError::NotAnImage(PathBuf::from(path)))?; - let root = path + let path = path .parent() .ok_or(ReadFileError::FileNotFound(PathBuf::from(path)))?; @@ -48,16 +42,17 @@ 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 { - let thumbnail_path = Thumbnail::thumbnail_path(&self.id, &self.root); - std::fs::File::open(thumbnail_path.clone()).map_err(|err| { + std::fs::File::open(self.path.clone()).map_err(|err| { if err.kind() == std::io::ErrorKind::NotFound { ReadFileError::FileNotFound } else { @@ -66,9 +61,8 @@ impl Thumbnail { }) } - pub fn delete(&self) -> Result<(), WriteFileError> { - let path = Thumbnail::thumbnail_path(&self.id, &self.root); - remove_file(path).map_err(WriteFileError::from) + pub fn delete(self) -> Result<(), WriteFileError> { + remove_file(self.path).map_err(WriteFileError::from) } } @@ -79,9 +73,12 @@ mod test { #[test] fn it_creates_a_thumbnail_if_one_does_not_exist() { - let _ = FileCleanup(PathBuf::from("fixtures/.thumbnails/rawr.png")); - let _ = - Thumbnail::open("rawr.png", Path::new("fixtures")).expect("thumbnail open must work"); - assert!(Path::new("fixtures/.thumbnails/rawr.png").is_file()); + let _ = FileCleanup(PathBuf::from("var/rawr.tn.png")); + let _ = Thumbnail::open( + PathBuf::from("fixtures/rawr.png"), + PathBuf::from("var/rawr.tn.png"), + ) + .expect("thumbnail open must work"); + assert!(Path::new("var/rawr.tn.png").is_file()); } }