add serving files

This commit is contained in:
Jackzie 2025-04-15 13:05:03 -05:00
parent ab60024f03
commit 403cf26426
12 changed files with 153 additions and 61 deletions

21
server/src/helpers.rs Normal file
View file

@ -0,0 +1,21 @@
use std::fmt::Write;
use anyhow::anyhow;
use rocket_dyn_templates::handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError, RenderErrorReason};
pub(crate) fn bytes(h: &Helper< '_>, _: &Handlebars<'_>, _: &Context, rc:
&mut RenderContext<'_, '_>, out: &mut dyn Output) -> HelperResult {
// get parameter from helper or throw an error
let param = h.param(0).and_then(|v| v.value().as_i64()).unwrap_or(0);
let output = humanize_bytes::humanize_bytes_decimal!(param);
out.write(&*output)?;
Ok(())
}
pub(crate) fn debug(h: &Helper< '_>, _: &Handlebars<'_>, _: &Context, rc:
&mut RenderContext<'_, '_>, out: &mut dyn Output) -> HelperResult {
let param = h.param(0)
.and_then(|v| v.value().as_object())
.ok_or::<RenderError>(RenderErrorReason::ParamNotFoundForIndex("", 0).into())?;
let output = serde_json::to_string(param).unwrap();
out.write(&output)?;
Ok(())
}

View file

@ -3,7 +3,8 @@ use log::{debug, error, info, trace, warn};
use rocket::{catch, launch, routes, Request, State};
use rocket::data::ByteUnit;
use rocket::fs::{relative, FileServer};
use rocket_dyn_templates::handlebars::Handlebars;
use rocket::futures::AsyncWriteExt;
use rocket_dyn_templates::handlebars::{handlebars_helper, Context, Handlebars, Helper, HelperResult, Output, RenderContext};
use rocket_dyn_templates::Template;
use sqlx::{migrate, Pool, Postgres};
use sqlx::postgres::PgPoolOptions;
@ -24,6 +25,7 @@ mod user;
mod models;
mod managers;
mod objs;
mod helpers;
pub type DB = Pool<Postgres>;
@ -69,11 +71,13 @@ async fn rocket() -> _ {
api::library::move_file, api::library::upload_file, api::library::download_file, api::library::list_files, api::library::get_file, api::library::delete_file,
])
.mount("/", routes![
ui::user::index, ui::user::list_library_files
ui::user::index, ui::user::list_library_files, ui::user::get_library_file
])
.attach(Template::custom(|engines| {
let _ = engines
.handlebars;
let hb = &mut engines.handlebars;
hb.register_helper("bytes", Box::new(helpers::bytes));
hb.register_helper("debug", Box::new(helpers::debug));
}))
}

View file

@ -1,5 +1,9 @@
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use anyhow::Error;
use rocket::response::stream::ReaderStream;
use tokio::io::BufStream;
use crate::managers::repos::RepoContainer;
use crate::{models, DB};
use crate::models::library::LibraryModel;
@ -24,6 +28,11 @@ impl Library {
&self.model
}
pub async fn get_read_stream(&self, rel_path: &PathBuf) -> Result<BufReader<File>, anyhow::Error> {
let mut repo = self.repo.read().await;
repo.backend.get_read_stream(&self.model.id.to_string(), rel_path)
}
pub async fn write_file(&self, rel_path: &PathBuf, contents: &[u8]) -> Result<(), anyhow::Error> {
let mut repo = self.repo.read().await;
repo.backend.write_file(&self.model.id.to_string(), rel_path, contents)

View file

@ -1,6 +1,12 @@
use std::io::Cursor;
use std::path::PathBuf;
use std::sync::Arc;
use rocket::{get, State};
use rocket::{get, Response, State};
use rocket::fs::NamedFile;
use rocket::http::{ContentType, Header};
use rocket::http::hyper::body::Buf;
use rocket::response::{status, Responder};
use rocket::response::stream::ByteStream;
use rocket::serde::json::Json;
use rocket_dyn_templates::{context, Template};
use tokio::sync::Mutex;
@ -13,7 +19,9 @@ pub async fn index() -> Template {
}
#[get("/libraries/<library_id>/<_>/<path..>")]
pub async fn list_library_files(libraries: &State<Arc<Mutex<LibraryManager>>>, library_id: &str, path: PathBuf) -> Result<Template, ResponseError> {
pub async fn list_library_files(libraries: &State<Arc<Mutex<LibraryManager>>>, library_id: &str, path: PathBuf)
-> Result<Template, ResponseError>
{
let libs = libraries.lock().await;
let library = libs.get(library_id).await?;
let files = library.list_files(&PathBuf::from(path)).await
@ -26,4 +34,49 @@ pub async fn list_library_files(libraries: &State<Arc<Mutex<LibraryManager>>>, l
library: library.model(),
files: files
}))
}
#[derive(Responder)]
#[response(status = 200)]
struct FileAttachment {
content: Vec<u8>,
// Override the Content-Type declared above.
content_type: ContentType,
disposition: Header<'static>,
}
#[get("/file/<library_id>/<path..>")]
pub async fn get_library_file<'a>(libraries: &State<Arc<Mutex<LibraryManager>>>, library_id: &str, path: PathBuf)
-> Result<FileAttachment, ResponseError>
{
let libs = libraries.lock().await;
let library = libs.get(library_id).await?;
match library.read_file(&PathBuf::from(&path)).await
.map_err(|e| ResponseError::GenericError)?
{
None => {
Err(ResponseError::NotFound(JsonErrorResponse {
code: "FILE_NOT_FOUND".to_string(),
message: "Requested file does not exist".to_string()
}))
}
Some(contents) => {
// TODO: headers?
let file_name = path.file_name().unwrap().to_string_lossy();
let ext = path.extension().unwrap().to_string_lossy();
let file_type = ContentType::from_extension(&ext);
// let res = Response::build()
// .header(file_type.unwrap_or(ContentType::Binary))
// .header(Header::new("Content-Disposition", format!("attachment; filename=\"{}\"", file_name)))
// .sized_body(contents.len(), Cursor::new(contents))
// .finalize();
Ok(FileAttachment {
content: contents,
content_type: file_type.unwrap_or(ContentType::Binary),
disposition: Header::new("Content-Disposition", format!("filename=\"{}\"", file_name))
})
// Ok(res)
}
}
}

View file

@ -1,6 +1,8 @@
mod local;
mod s3;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use anyhow::{anyhow, Error};
use int_enum::IntEnum;
@ -68,4 +70,5 @@ pub trait StorageBackend {
fn delete_file(&self, library_id: &str, rel_path: &PathBuf) -> Result<(), anyhow::Error>;
fn move_file(&self, library_id: &str, rel_path: &PathBuf, new_rel_path: &PathBuf) -> Result<(), Error>;
fn get_read_stream(&self, library_id: &str, rel_path: &PathBuf,) -> Result<BufReader<File>, Error>;
}

View file

@ -1,4 +1,6 @@
use std::env::join_paths;
use std::fs::File;
use std::io::BufReader;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Error};
@ -35,6 +37,12 @@ fn get_path(folder_root: &PathBuf, library_id: &str, mut path: &Path) -> Result<
}
impl StorageBackend for LocalStorage {
fn get_read_stream(&self, library_id: &str, rel_path: &PathBuf,) -> Result<BufReader<File>, Error> {
let path = get_path(&self.folder_root, library_id, rel_path)?;
let file = File::open(path)?;
Ok(BufReader::new(file))
}
fn write_file(&self, library_id: &str, rel_path: &PathBuf, contents: &[u8]) -> Result<(), Error> {
let path = get_path(&self.folder_root, library_id, rel_path)?;
std::fs::write(path, contents).map_err(|e| anyhow!(e))