use std::error::Error; use std::net::SocketAddr; use std::path::PathBuf; use log::warn; use prometheus::Encoder; use rust_embed::RustEmbed; use serde::Deserialize; use warp; use warp::http::header::{HeaderMap, HeaderValue}; use warp::reject::Rejection; use warp::Filter; use crate::library::Library; fn metrics() -> impl Filter + Clone { let mut text_headers = HeaderMap::new(); text_headers.insert("content-type", HeaderValue::from_static("text/plain")); warp::path("metrics") .map(|| { let mut buffer = Vec::new(); let encoder = prometheus::TextEncoder::new(); // Gather the metrics. let metric_families = prometheus::gather(); // Encode them to send. encoder.encode(&metric_families, &mut buffer).unwrap(); // TODO(wathiede): see if there's a wrapper like html() buffer }) .with(warp::reply::with::headers(text_headers)) } // TODO(wathiede): add caching for hashed files. Add at least etag for everything. fn index(path: warp::path::FullPath) -> Result { let path = path.as_str(); let path = if path.ends_with("/") { format!("{}index.html", path.to_string()) } else { path.to_string() }; let path = &path[1..]; match Asset::get(path) { Some(bytes) => { let mime = mime_guess::from_path(path).first_or_octet_stream(); Ok(warp::http::Response::builder() .header("Content-Type", mime.essence_str()) .body(bytes.into_owned())) } None => Err(warp::reject::not_found()), } } fn albums(lib: Library) -> Result { let albums = lib.albums().map_err(|e| { warn!("Couldn't find albums: {}", e); warp::reject::not_found() })?; Ok(warp::reply::json(&albums)) } fn album(lib: Library, id: String) -> Result { let album = lib.album(&id).map_err(|e| { warn!("Couldn't find album {}: {}", id, e); warp::reject::not_found() })?; Ok(warp::reply::json(&album)) } #[derive(Debug, Deserialize)] struct ImageParams { w: Option, h: Option, } fn image( lib: Library, media_items_id: String, params: ImageParams, ) -> Result { match lib.thumbnail( &media_items_id, (params.w.unwrap_or(0), params.h.unwrap_or(0)), ) { None => { warn!("Couldn't find original {}", &media_items_id); Err(warp::reject::not_found()) } Some(bytes) => Ok(warp::http::Response::builder() .header("Content-Type", "image/jpeg") .body(bytes)), } } #[derive(RustEmbed)] #[folder = "react-debug/build/"] struct Asset; pub fn run(addr: SocketAddr, root: PathBuf) -> Result<(), Box> { let lib = Library::new(root)?; let lib = warp::any().map(move || lib.clone()); let index = warp::get2().and(warp::path::full()).and_then(index); let albums = warp::path("albums").and(lib.clone()).and_then(albums); let album = warp::path("album") .and(lib.clone()) .and(warp::path::param()) .and_then(album); let image = warp::path("image") .and(lib.clone()) .and(warp::path::param()) .and(warp::query::()) .and_then(image); let api = albums.or(album).or(image); let api = warp::path("api").and(api); // Fallback, always keep this last. let api = api.or(index); let api = api.with(warp::log("photosync")); // We don't want metrics & heath checking filling up the logs, so we add this handler after // wrapping with the log filter. let routes = metrics().or(api); warp::serve(routes).run(addr); Ok(()) }