Provide a unified interface for the File and Thumbnail
This commit is contained in:
parent
809861a38e
commit
98a07ce03e
|
@ -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<Response> {
|
|||
|
||||
fn serve_file(
|
||||
info: FileInfo,
|
||||
file: File,
|
||||
file: impl HasContent,
|
||||
old_etags: Option<String>,
|
||||
) -> http::Result<http::Response<Vec<u8>>> {
|
||||
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<String>| 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<String>| 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()
|
||||
|
|
|
@ -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<Vec<u8>, ReadFileError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn hash_content(&self) -> Result<HexString, ReadFileError> {
|
||||
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<Vec<u8>, ReadFileError> {
|
||||
let mut content: Vec<u8> = 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/")),
|
||||
)
|
||||
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/")),
|
||||
)
|
||||
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),
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -64,6 +64,10 @@ pub enum PathError {
|
|||
InvalidPath,
|
||||
}
|
||||
|
||||
pub trait HasContent {
|
||||
fn content(&self) -> Result<Vec<u8>, ReadFileError>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PathResolver {
|
||||
base: PathBuf,
|
||||
|
@ -159,6 +163,19 @@ impl From<&str> for FileId {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<FileId> 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<FileInfo, ReadFileError> {
|
||||
pub fn get_metadata(&self, id: &FileId) -> Result<FileInfo, ReadFileError> {
|
||||
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<F>(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,42 +302,21 @@ 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();
|
||||
|
||||
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");
|
||||
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);
|
||||
|
@ -301,17 +324,27 @@ mod test {
|
|||
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() {}
|
||||
|
|
|
@ -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<Thumbnail, ReadFileError> {
|
||||
let s = Thumbnail { path: path.clone() };
|
||||
|
||||
if !s.path.exists() {
|
||||
return Err(ReadFileError::FileNotFound(path));
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn from_path(path: &Path) -> Result<Thumbnail, ReadFileError> {
|
||||
let id = path
|
||||
|
@ -61,6 +73,17 @@ impl Thumbnail {
|
|||
*/
|
||||
}
|
||||
|
||||
impl HasContent for Thumbnail {
|
||||
fn content(&self) -> Result<Vec<u8>, ReadFileError> {
|
||||
let mut content: Vec<u8> = 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::*;
|
||||
|
|
Loading…
Reference in New Issue