use axum::{ extract::{Path, State, Request}, response::{IntoResponse, Response}, http::header, }; use std::sync::Arc; use tower_http::services::ServeFile; use tower::ServiceExt; use crate::repo::DepotRepo; use crate::errors::DepotError; use std::fs; use httpdate::fmt_http_date; use std::time::{SystemTime, UNIX_EPOCH}; pub async fn get_file( State(repo): State>, Path((publisher, _algo, digest)): Path<(String, String, String)>, req: Request, ) -> Result { let path = repo.get_file_path(&publisher, &digest) .ok_or_else(|| DepotError::Repo(libips::repository::RepositoryError::NotFound(digest.clone())))?; let service = ServeFile::new(path); let result = service.oneshot(req).await; match result { Ok(mut res) => { // Add caching headers let max_age = repo.cache_max_age(); res.headers_mut().insert(header::CACHE_CONTROL, header::HeaderValue::from_str(&format!("public, max-age={}", max_age)).unwrap()); // ETag from digest res.headers_mut().insert(header::ETAG, header::HeaderValue::from_str(&format!("\"{}\"", digest)).unwrap()); // Last-Modified from fs metadata if let Some(body_path) = res.extensions().get::().cloned() { if let Ok(meta) = fs::metadata(&body_path) { if let Ok(mtime) = meta.modified() { let lm = fmt_http_date(mtime); res.headers_mut().insert(header::LAST_MODIFIED, header::HeaderValue::from_str(&lm).unwrap()); } } } // Fallback: use now if extension not present (should rarely happen) if !res.headers().contains_key(header::LAST_MODIFIED) { let now = SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|_| SystemTime::now()).unwrap_or_else(SystemTime::now); let lm = fmt_http_date(now); res.headers_mut().insert(header::LAST_MODIFIED, header::HeaderValue::from_str(&lm).unwrap()); } Ok(res.into_response()) }, Err(e) => Err(DepotError::Server(e.to_string())), } }