/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2023 metamuffin */ use log::debug; use markup::Render; use rocket::{ futures::FutureExt, http::{ContentType, Header, Status}, response::{self, Responder}, Request, Response, }; use std::{ collections::hash_map::DefaultHasher, future::Future, hash::{Hash, Hasher}, io::Cursor, os::unix::prelude::MetadataExt, pin::Pin, }; use tokio::{fs::File, io::AsyncRead}; pub mod account; pub mod browser; pub mod error; pub mod home; pub mod layout; pub mod node; pub mod player; pub mod style; pub mod assets; pub struct HtmlTemplate<'a>(pub markup::DynRender<'a>); impl<'r> Responder<'r, 'static> for HtmlTemplate<'_> { fn respond_to(self, _req: &'r Request<'_>) -> response::Result<'static> { let mut out = String::new(); self.0.render(&mut out).unwrap(); Response::build() .header(ContentType::HTML) .sized_body(out.as_bytes().len(), Cursor::new(out)) .ok() } } pub struct Defer(Pin + Send>>); impl AsyncRead for Defer { fn poll_read( mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &mut tokio::io::ReadBuf<'_>, ) -> std::task::Poll> { match self.0.poll_unpin(cx) { std::task::Poll::Ready(r) => { buf.put_slice(r.as_bytes()); std::task::Poll::Ready(Ok(())) } std::task::Poll::Pending => std::task::Poll::Pending, } } } pub struct CacheControlFile(File, String); impl CacheControlFile { pub async fn new(f: File) -> Self { let meta = f.metadata().await.unwrap(); let modified = meta.mtime(); let mut h = DefaultHasher::new(); modified.hash(&mut h); let tag = format!("{:0>16x}", h.finish()); Self(f, tag) } } impl<'r> Responder<'r, 'static> for CacheControlFile { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { let Self(file, tag) = self; if req.headers().get_one("if-none-match") == Some(&tag) { debug!("file cache: not modified"); Response::build() .status(Status::NotModified) .header(Header::new("cache-control", "private")) .ok() } else { debug!("file cache: transfer"); Response::build() .status(Status::Ok) .header(Header::new("cache-control", "private")) .header(Header::new("etag", tag)) .streamed_body(file) .ok() } } }