Load file by ID

This commit is contained in:
Savanni D'Gerinel 2023-09-23 19:15:56 -04:00
parent 89a1aa7ee5
commit 88938e44c8
5 changed files with 104 additions and 63 deletions

View File

@ -30,7 +30,7 @@ mod middleware;
mod pages; mod pages;
mod store; mod store;
pub use store::{FileInfo, Store}; pub use store::{File, FileId, FileInfo, Store};
/* /*
fn is_admin(resource: &ResourceName, permissions: &Permissions) -> bool { fn is_admin(resource: &ResourceName, permissions: &Permissions) -> bool {
@ -229,23 +229,24 @@ fn script(_: &mut Request) -> IronResult<Response> {
fn serve_file( fn serve_file(
info: FileInfo, info: FileInfo,
mut file: std::fs::File, file: File,
old_etags: Option<String>, old_etags: Option<String>,
) -> http::Result<http::Response<Vec<u8>>> { ) -> http::Result<http::Response<Vec<u8>>> {
let mut content = Vec::new();
match old_etags { match old_etags {
Some(old_etags) if old_etags != info.hash => warp::http::Response::builder() Some(old_etags) if old_etags != info.hash => warp::http::Response::builder()
.header("content-type", info.file_type) .header("content-type", info.file_type)
.status(StatusCode::NOT_MODIFIED) .status(StatusCode::NOT_MODIFIED)
.body(content), .body(vec![]),
_ => { _ => match file.content() {
let _ = file.read_to_end(&mut content); Ok(content) => warp::http::Response::builder()
warp::http::Response::builder()
.header("content-type", info.file_type) .header("content-type", info.file_type)
.header("etag", info.hash) .header("etag", info.hash)
.status(StatusCode::OK) .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<String>| match app move |id: String, old_etags: Option<String>| match app
.read() .read()
.unwrap() .unwrap()
.get_thumbnail(&id) .get_thumbnail(FileId::from(id))
{ {
Ok((info, file)) => serve_file(info, file, old_etags), Ok((info, file)) => serve_file(info, file, old_etags),
Err(_err) => warp::http::Response::builder() Err(_err) => warp::http::Response::builder()
@ -373,7 +374,11 @@ pub async fn main() {
.and(warp::header::optional::<String>("if-none-match")) .and(warp::header::optional::<String>("if-none-match"))
.map({ .map({
let app = app.clone(); let app = app.clone();
move |id: String, old_etags: Option<String>| match app.read().unwrap().get_file(&id) { move |id: String, old_etags: Option<String>| match app
.read()
.unwrap()
.get_file(FileId::from(id))
{
Ok((info, file)) => serve_file(info, file, old_etags), Ok((info, file)) => serve_file(info, file, old_etags),
Err(_err) => warp::http::Response::builder() Err(_err) => warp::http::Response::builder()
.status(StatusCode::NOT_FOUND) .status(StatusCode::NOT_FOUND)

View File

@ -27,19 +27,29 @@ 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(filename.clone()); let extension = PathBuf::from(filename)
let path = PathResolver::try_from(path).map_err(|_| WriteFileError::InvalidPath)?; .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() .first_or_text_plain()
.essence_str() .essence_str()
.to_owned(); .to_owned();
let info = FileInfo { let info = FileInfo {
id: id.clone(),
size: 0, size: 0,
created: Utc::now(), created: Utc::now(),
file_type, file_type,
hash: "".to_owned(), hash: "".to_owned(),
extension,
}; };
let mut md_file = std::fs::File::create(path.metadata_path())?; let mut md_file = std::fs::File::create(path.metadata_path())?;
@ -49,21 +59,12 @@ impl File {
} }
pub fn load(resolver: PathResolver) -> Result<Self, ReadFileError> { pub fn load(resolver: PathResolver) -> Result<Self, ReadFileError> {
/* let info = FileInfo::load(resolver.metadata_path())?;
Ok(Self { Ok(Self {
id: FileId::from( id: info.id.clone(),
resolver
.file_path()
.file_stem()
.unwrap()
.to_string_lossy()
.to_owned(),
),
path: resolver, path: resolver,
info: FileInfo::load(resolver.metadata_path())?, info,
}) })
*/
unimplemented!()
} }
pub fn set_content(&mut self, content: Vec<u8>) -> Result<(), WriteFileError> { pub fn set_content(&mut self, content: Vec<u8>) -> Result<(), WriteFileError> {
@ -74,6 +75,8 @@ impl File {
let mut md_file = std::fs::File::create(self.path.metadata_path())?; let mut md_file = std::fs::File::create(self.path.metadata_path())?;
md_file.write(&serde_json::to_vec(&self.info)?)?; md_file.write(&serde_json::to_vec(&self.info)?)?;
Thumbnail::open(self.path.file_path(), self.path.thumbnail_path())?;
Ok(()) Ok(())
} }
@ -245,7 +248,7 @@ mod test {
fn it_raises_an_error_when_file_not_found() { 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("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),
} }
} }

View File

@ -1,3 +1,5 @@
use crate::FileId;
use super::{ReadFileError, WriteFileError}; use super::{ReadFileError, WriteFileError};
use chrono::prelude::*; use chrono::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -9,16 +11,19 @@ use std::{
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FileInfo { pub struct FileInfo {
pub id: FileId,
pub size: usize, pub size: usize,
pub created: DateTime<Utc>, pub created: DateTime<Utc>,
pub file_type: String, pub file_type: String,
pub hash: String, pub hash: String,
pub extension: String,
} }
impl FileInfo { impl FileInfo {
pub fn load(path: PathBuf) -> Result<Self, ReadFileError> { pub fn load(path: PathBuf) -> Result<Self, ReadFileError> {
let mut content: Vec<u8> = Vec::new(); let mut content: Vec<u8> = 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)?; file.read_to_end(&mut content)?;
let js = serde_json::from_slice(&content)?; let js = serde_json::from_slice(&content)?;
@ -96,7 +101,7 @@ impl FileInfo {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::store::{utils::FileCleanup, PathResolver}; use crate::store::{utils::FileCleanup, FileId, PathResolver};
use std::convert::TryFrom; use std::convert::TryFrom;
#[test] #[test]
@ -108,10 +113,12 @@ mod test {
let created = Utc::now(); let created = Utc::now();
let info = FileInfo { let info = FileInfo {
id: FileId("temp-id".to_owned()),
size: 23777, size: 23777,
created, created,
file_type: "image/png".to_owned(), file_type: "image/png".to_owned(),
hash: "abcdefg".to_owned(), hash: "abcdefg".to_owned(),
extension: "png".to_owned(),
}; };
info.save(resolver.metadata_path()).unwrap(); info.save(resolver.metadata_path()).unwrap();

View File

@ -1,3 +1,4 @@
use serde::{Deserialize, Serialize};
use std::{ use std::{
convert::TryFrom, convert::TryFrom,
ops::Deref, ops::Deref,
@ -39,7 +40,7 @@ pub enum WriteFileError {
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ReadFileError { pub enum ReadFileError {
#[error("file not found")] #[error("file not found")]
FileNotFound, FileNotFound(PathBuf),
#[error("path is not a file")] #[error("path is not a file")]
NotAFile, NotAFile,
@ -143,7 +144,7 @@ impl TryFrom<&Path> for PathResolver {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FileId(String); pub struct FileId(String);
impl From<String> for FileId { impl From<String> for FileId {
@ -197,7 +198,7 @@ impl Store {
Ok(file) 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)?; let f = File::open(&id, &self.files_root)?;
f.delete() f.delete()
@ -205,36 +206,36 @@ impl Store {
unimplemented!() unimplemented!()
} }
pub fn get_metadata(&self, id: String) -> Result<FileInfo, ReadFileError> { pub fn get_metadata(&self, id: FileId) -> Result<FileInfo, ReadFileError> {
// FileInfo::open(&id, &self.files_root) let mut path = self.files_root.clone();
unimplemented!() 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> { pub fn get_file(&self, id: FileId) -> Result<(FileInfo, File), ReadFileError> {
/* let info = self.get_metadata(id.clone())?;
let f = File::open(&id, &self.files_root)?;
let info = f.info(); let resolver = PathResolver {
let stream = f.stream()?; base: self.files_root.clone(),
Ok((info, stream)) id: (*id).clone(),
*/ extension: info.extension.clone(),
unimplemented!() };
let f = File::load(resolver)?;
Ok((info, f))
} }
pub fn get_thumbnail(&self, id: &str) -> Result<(FileInfo, std::fs::File), ReadFileError> { pub fn get_thumbnail(&self, id: FileId) -> Result<(FileInfo, File), ReadFileError> {
/*
let f = File::open(id, &self.files_root)?;
let stream = f.thumbnail().stream()?;
Ok((f.info(), stream))
*/
unimplemented!() unimplemented!()
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::store::utils::FileCleanup;
use super::*; use super::*;
use crate::store::utils::FileCleanup;
use cool_asserts::assert_matches;
use std::io::Read; use std::io::Read;
#[test] #[test]
@ -264,17 +265,47 @@ mod test {
let mut store = Store::new(PathBuf::from("var/")); let mut store = Store::new(PathBuf::from("var/"));
let file_record = store.add_file("rawr.png".to_owned(), buf).unwrap(); 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 _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)));
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", *file_record.id)).exists());
assert!(PathBuf::from(format!("var/{}.png.json", *file_record.id)).exists()); assert!(PathBuf::from(format!("var/{}.json", *file_record.id)).exists());
assert!(PathBuf::from(format!("var/{}.png.tn", *file_record.id)).exists()); assert!(PathBuf::from(format!("var/{}.tn.png", *file_record.id)).exists());
} }
#[test] #[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] #[test]
fn sets_up_thumbnail_for_file() {} fn sets_up_thumbnail_for_file() {}

View File

@ -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, ReadFileError> { pub fn stream(&self) -> Result<std::fs::File, ReadFileError> {
std::fs::File::open(self.path.clone()).map_err(|err| { std::fs::File::open(self.path.clone()).map_err(|err| {
if err.kind() == std::io::ErrorKind::NotFound { if err.kind() == std::io::ErrorKind::NotFound {
@ -60,10 +52,13 @@ impl Thumbnail {
} }
}) })
} }
*/
/*
pub fn delete(self) -> Result<(), WriteFileError> { pub fn delete(self) -> Result<(), WriteFileError> {
remove_file(self.path).map_err(WriteFileError::from) remove_file(self.path).map_err(WriteFileError::from)
} }
*/
} }
#[cfg(test)] #[cfg(test)]