Provide a unified interface for the File and Thumbnail

This commit is contained in:
Savanni D'Gerinel 2023-09-23 20:20:35 -04:00
parent 88938e44c8
commit 3e87e13526
5 changed files with 135 additions and 82 deletions

View File

@ -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()

View File

@ -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/")),
)
.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),

View File

@ -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());

View File

@ -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,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() {}

View File

@ -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::*;