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