/* 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) 2025 metamuffin */ use log::debug; use rocket::{ http::{Header, Status}, response::{self, Responder}, Request, Response, }; use std::{ hash::{DefaultHasher, Hash, Hasher}, os::unix::fs::MetadataExt, path::Path, }; use tokio::fs::File; pub struct CacheControlFile(File, String); impl CacheControlFile { pub async fn new_cachekey(p: &Path) -> anyhow::Result { let tag = p.file_name().unwrap().to_str().unwrap().to_owned(); let f = File::open(p).await?; Ok(Self(f, tag)) } pub async fn new_mtime(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")) .header(Header::new("etag", tag)) .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() } } }