From cd10c64a1f96703894de9e40a95fd81cc50d244a Mon Sep 17 00:00:00 2001 From: JMARyA Date: Tue, 24 Dec 2024 15:50:48 +0100 Subject: [PATCH] update --- examples/static.rs | 27 ++++++++++ src/lib.rs | 4 +- src/request/assets.rs | 112 ++++++++++++++++++++++++++++-------------- 3 files changed, 104 insertions(+), 39 deletions(-) create mode 100644 examples/static.rs diff --git a/examples/static.rs b/examples/static.rs new file mode 100644 index 0000000..8b89946 --- /dev/null +++ b/examples/static.rs @@ -0,0 +1,27 @@ +use based::get_pg; +use based::request::RequestContext; + +use rocket::get; +use rocket::response::Responder; +use rocket::routes; + +#[get("/")] +pub async fn index_page<'r>(ctx: RequestContext) -> impl Responder<'r, 'static> { + based::request::assets::DataResponse::new( + include_bytes!("../Cargo.toml").to_vec(), + "text/toml", + Some(60 * 60 * 3), + ) +} + +#[rocket::launch] +async fn launch() -> _ { + // Logging + env_logger::init(); + + // Database + let pg = get_pg!(); + // sqlx::migrate!("./migrations").run(pg).await.unwrap(); + + rocket::build().mount("/", routes![index_page]) +} diff --git a/src/lib.rs b/src/lib.rs index 9eb6a86..6cd0577 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,11 +2,11 @@ use tokio::sync::OnceCell; pub mod auth; pub mod format; +#[cfg(feature = "htmx")] +pub mod htmx; pub mod page; pub mod request; pub mod result; -#[cfg(feature = "htmx")] -pub mod htmx; // TODO : API Pagination? // TODO : CORS? diff --git a/src/request/assets.rs b/src/request/assets.rs index 3b3e2e8..928a0fd 100644 --- a/src/request/assets.rs +++ b/src/request/assets.rs @@ -1,49 +1,87 @@ -// TODO : Implement +use rocket::Request; +use rocket::Response; +use rocket::http::Header; +use rocket::http::Status; +use rocket::response::Responder; +use std::io::Cursor; -/* +// TODO: Implement file based response -#[get("/video/raw?")] -pub async fn video_file( - v: &str, - library: &State, -) -> Option<(Status, (ContentType, Vec))> { - let video = if let Some(video) = library.get_video_by_id(v).await { - video - } else { - library.get_video_by_youtube_id(v).await.unwrap() - }; +pub struct DataResponse { + data: Vec, + content_type: String, + cache_duration: Option, +} - if let Ok(mut file) = File::open(&video.path).await { - let mut buf = Vec::with_capacity(51200); - file.read_to_end(&mut buf).await.ok()?; - let content_type = if video.path.ends_with("mp4") { - ContentType::new("video", "mp4") +impl DataResponse { + pub fn new(data: Vec, content_type: &str, cache_duration: Option) -> Self { + Self { + data, + content_type: content_type.to_string(), + cache_duration, + } + } +} + +#[rocket::async_trait] +impl<'r> Responder<'r, 'static> for DataResponse { + fn respond_to(self, req: &'r Request<'_>) -> rocket::response::Result<'static> { + // Handle Range requests + if let Some(range) = req.headers().get_one("Range") { + if let Some((start, end)) = parse_range_header(range, self.data.len()) { + // TODO : Reject invalid ranges + // TODO : Multiple ranges? + + let sliced_data = &self.data[start..=end]; + return Ok(Response::build() + .header(Header::new( + "Content-Range", + format!("bytes {}-{}/{}", start, end, self.data.len()), + )) + .header(Header::new("Accept-Ranges", "bytes")) + .header(Header::new("Content-Type", self.content_type.clone())) + .status(Status::PartialContent) + .streamed_body(Cursor::new(sliced_data.to_vec())) + .finalize()); + } + } + + // Add caching headers for static files + let cache_control_header = if let Some(duration) = self.cache_duration { + Header::new("Cache-Control", format!("public, max-age={}", duration)) } else { - ContentType::new("video", "webm") + Header::new("Cache-Control", "no-cache") }; - return Some((Status::Ok, (content_type, buf))); + Ok(Response::build() + .header(cache_control_header) + .header(Header::new("Accept-Ranges", "bytes")) + .header(Header::new("Content-Type", self.content_type)) + .streamed_body(Cursor::new(self.data)) + .finalize()) } - - None } -#[get("/video/thumbnail?")] -pub async fn video_thumbnail( - v: &str, - library: &State, -) -> Option<(Status, (ContentType, Vec))> { - let video = if let Some(video) = library.get_video_by_id(v).await { - video - } else { - library.get_video_by_youtube_id(v).await.unwrap() - }; - - if let Some(data) = library.get_thumbnail(&video).await { - return Some((Status::Ok, (ContentType::PNG, data))); +/// Parse the Range header and return the start and end indices. +fn parse_range_header(range: &str, total_len: usize) -> Option<(usize, usize)> { + if !range.starts_with("bytes=") { + return None; } - None -} + let range = &range[6..]; + let parts: Vec<&str> = range.split('-').collect(); -*/ + if parts.len() != 2 { + return None; + } + + let start = parts[0].parse::().ok(); + let end = parts[1].parse::().ok(); + + match (start, end) { + (Some(start), Some(end)) if start <= end && end < total_len => Some((start, end)), + (Some(start), None) if start < total_len => Some((start, total_len - 1)), + (None, Some(end)) if end < total_len => Some((0, end)), + _ => None, + } +}