diff --git a/Cargo.toml b/Cargo.toml index b506a28..b627b77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,4 +31,3 @@ reqwest = { version = "0.11", features = ["blocking"] } [features] cache = [] -htmx = [] diff --git a/examples/static.rs b/examples/static.rs deleted file mode 100644 index 8b89946..0000000 --- a/examples/static.rs +++ /dev/null @@ -1,27 +0,0 @@ -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 6cd0577..10dda8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,12 +2,14 @@ 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; +pub mod htmx; +// TODO : Cache Headers +// TODO : Refactor Responders +// TODO : Streaming Responses + Ranges // TODO : API Pagination? // TODO : CORS? // TODO : CSRF? diff --git a/src/request/assets.rs b/src/request/assets.rs index 928a0fd..3b3e2e8 100644 --- a/src/request/assets.rs +++ b/src/request/assets.rs @@ -1,87 +1,49 @@ -use rocket::Request; -use rocket::Response; -use rocket::http::Header; -use rocket::http::Status; -use rocket::response::Responder; -use std::io::Cursor; +// TODO : Implement -// TODO: Implement file based response +/* -pub struct DataResponse { - data: Vec, - content_type: String, - cache_duration: Option, -} +#[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() + }; -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)) + 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") } else { - Header::new("Cache-Control", "no-cache") + ContentType::new("video", "webm") }; - 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()) + return Some((Status::Ok, (content_type, buf))); } + + None } -/// 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; +#[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))); } - 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, - } + None } + +*/