Load file by ID
This commit is contained in:
parent
89a1aa7ee5
commit
88938e44c8
|
@ -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)
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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() {}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
Loading…
Reference in New Issue