From 83ba4fe37a1184b998be09b9cbe53a22c7ba9e3b Mon Sep 17 00:00:00 2001 From: alyx Date: Mon, 1 Apr 2024 20:27:44 -0400 Subject: Move caching to src/cache/; Finalize API parsing fixes Font and user cache code has been moved to special files, independant from src/config.rs API parsing changes have been properly tested, and last.fm API JSON is now trace-logged for debugging convenience. --- src/cache.rs | 9 ++-- src/cache/font.rs | 58 +++++++++++++++++++++++++ src/cache/user.rs | 101 +++++++++++++++++++++++++++++++++++++++++++ src/config.rs | 124 ++++++----------------------------------------------- src/ctx.rs | 10 ++--- src/deserialize.rs | 16 +++---- src/font.rs | 51 ---------------------- src/http.rs | 9 ++++ src/lib.rs | 5 ++- src/main.rs | 18 ++++---- 10 files changed, 212 insertions(+), 189 deletions(-) create mode 100644 src/cache/font.rs create mode 100644 src/cache/user.rs delete mode 100644 src/font.rs create mode 100644 src/http.rs (limited to 'src') diff --git a/src/cache.rs b/src/cache.rs index a6f25fa..cf612b6 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,3 +1,6 @@ +pub mod font; +pub mod user; + use std::future::Future; use std::time::*; use std::collections::HashMap; @@ -31,10 +34,10 @@ where pub async fn get(&mut self, key: &K) -> Result<&V, (StatusCode, &'static str)> { if self.is_stale(key) { - log::trace!(target: "lfm::cache", "MISS : interval = {:?}", self.interval); + log::trace!("MISS : interval = {:?}", self.interval); self.renew(key).await } else { - log::trace!(target: "lfm::cache", "HIT : interval = {:?}", self.interval); + log::trace!("HIT : interval = {:?}", self.interval); Ok(&self.cache.get(key).unwrap().1) } } @@ -48,7 +51,7 @@ where pub fn is_stale(&self, key: &K) -> bool { if let Some((last_update, _)) = self.cache.get(key) { let now = Instant::now(); - log::trace!(target: "lfm::cache", "Key exists, last update {:?} ago.", now - *last_update); + log::trace!("Key exists, last update {:?} ago.", now - *last_update); now > (*last_update + self.interval) } else { true } diff --git a/src/cache/font.rs b/src/cache/font.rs new file mode 100644 index 0000000..c17247d --- /dev/null +++ b/src/cache/font.rs @@ -0,0 +1,58 @@ +use std::sync::Arc; +use std::time::Duration; +use std::sync::LazyLock; + +use tokio::sync::RwLock; +use reqwest::{StatusCode, Client}; + +use super::{CacheFuture, CacheGetter, Cache, AsyncCache}; +use crate::CONFIG; + +#[derive(serde::Deserialize, Debug, Default)] +#[serde(default)] +#[serde(rename = "kebab-case")] +pub struct FontQuery { + pub font: Option>, + pub include_font: Option>, + pub google_font: Option>, +// pub small_font: Option<()> +} + +pub type FontFuture = CacheFuture>; +pub type FontGetter = CacheGetter>; +pub type FontCache = Cache>; + +fn font_getter(fontname: &String) -> FontFuture { + let fontname = urlencoding::encode(fontname.as_ref()).to_string(); + Box::pin(async move { + let Some(google_api_key) = CONFIG.google_api_key.clone() + else { + unreachable!(); + }; + + let fontreq = HTTP.get(format!("https://www.googleapis.com/webfonts/v1/webfonts?key={}&family={fontname}", google_api_key)) + .send().await + .map_err(|e| {log::error!("Failed to get info for font `{fontname}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't connect to Google Fonts!")})?; + if fontreq.status() == StatusCode::NOT_FOUND { return Err((StatusCode::NOT_FOUND, "Font does not exist!")); } + if fontreq.status() == StatusCode::FORBIDDEN { + log::error!("Invalid Google API key in config!"); + return Err((StatusCode::SERVICE_UNAVAILABLE, "This instance is not configured to support Google Fonts properly, please use a different font.")); + } + + let cssreq = HTTP.get(format!("https://fonts.googleapis.com/css2?family={fontname}")) + .send().await + .map_err(|e| {log::error!("Failed to get CSS for font `{fontname}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't download font CSS!")})?; + + Ok(cssreq.text().await.unwrap().into()) + }) +} + +static HTTP: LazyLock = crate::http::lazy(); + +static FONT_CACHE: LazyLock = LazyLock::new(|| { + Arc::new(RwLock::new(AsyncCache::new(Duration::from_secs(86400), font_getter as FontGetter))) +}); + +pub async fn get_fontinfo(font: &String) -> Result, (StatusCode, &'static str)> { + FONT_CACHE.write().await.get_owned(font).await +} diff --git a/src/cache/user.rs b/src/cache/user.rs new file mode 100644 index 0000000..c3b6f04 --- /dev/null +++ b/src/cache/user.rs @@ -0,0 +1,101 @@ +use std::sync::Arc; +use std::time::Duration; +use std::sync::LazyLock; + +use tokio::sync::RwLock; +use reqwest::{StatusCode, Client}; + +use super::{CacheFuture, CacheGetter, Cache, AsyncCache}; +use crate::deserialize::{User, Track, TrackStub}; +use crate::CONFIG; + +type UserFuture = CacheFuture>; +type UserGetter = CacheGetter>; +type UserCache = Cache>; + +#[derive(Debug)] +enum Whitelist { + Exclusive{cache: UserCache}, + Open{default_cache: UserCache, whitelist_cache: UserCache} +} + +fn user_getter(username: &String) -> UserFuture { + use crate::deserialize::{GetUserInfo, GetRecentTracks, GetTrackInfo}; + + let username = urlencoding::encode(username.as_ref()).to_string(); + Box::pin(async move { + let userreq = HTTP.get(format!("https://ws.audioscrobbler.com/2.0/?method=user.getInfo&format=json&user={username}&api_key={}", CONFIG.lastfm_api_key)) + .send().await + .map_err(|e| {log::error!("Failed to get info for user `{username}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't connect to last.fm!")})?; + if userreq.status() == StatusCode::NOT_FOUND { return Err((StatusCode::NOT_FOUND, "User does not exist!")); } + + let userstr = userreq.text().await.unwrap(); + log::trace!("Got user.getUserInfo JSON for `{username}`: {userstr}"); + let userinfo = serde_json::from_str::(&userstr) + .map_err(|e| {log::error!("Couldn't parse user.getInfo for `{username}`: {e}"); (StatusCode::INTERNAL_SERVER_ERROR, "Couldn't parse user.getInfo!")})?.user; + + let tracksreq = HTTP.get(format!("https://ws.audioscrobbler.com/2.0/?method=user.getRecentTracks&format=json&extended=1&limit=1&user={username}&api_key={}", CONFIG.lastfm_api_key)) + .send().await + .map_err(|e| {log::error!("Failed to get tracks for user `{username}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't connect to last.fm!")})?; + if tracksreq.status() == StatusCode::NOT_FOUND { return Err((StatusCode::NOT_FOUND, "User does not exist!")); } + if tracksreq.status() == StatusCode::FORBIDDEN { return Err((StatusCode::FORBIDDEN, "You need to unprivate your song history!")); } + + let tracksstr = tracksreq.text().await.unwrap(); + log::trace!("Got user.getRecentTracks JSON for `{username}`: {tracksstr}"); + let trackstub = serde_json::from_str::(&tracksstr) + .map_err(|e| {log::error!("Couldn't parse user.getRecentTracks for `{username}`: {e}"); (StatusCode::INTERNAL_SERVER_ERROR, "Couldn't parse user.getRecentTracks!")})? + .recenttracks.track.into_iter().next().ok_or((StatusCode::UNPROCESSABLE_ENTITY, "You need to listen to some songs first!"))?; + + let trackreq = HTTP.get(format!("https://ws.audioscrobbler.com/2.0/?method=track.getInfo&format=json&username={username}&api_key={}&track={}&artist={}", CONFIG.lastfm_api_key, trackstub.name, trackstub.artist.name)) + .send().await + .map_err(|e| {log::error!("Failed to get tracks for user `{username}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't connect to last.fm!")})?; + if trackreq.status() == StatusCode::NOT_FOUND { return Err((StatusCode::NOT_FOUND, "Track does not exist!")); } + + let trackstr = trackreq.text().await.unwrap(); + log::trace!("Got user.getTrackInfo JSON for `{username}`: {trackstr}"); + let trackinfo = serde_json::from_str::(&trackstr) + .map_err(|e| {log::error!("Couldn't parse track.getInfo for `{}` by `{}` on behalf of {username}: {e}", trackstub.name, trackstub.artist.name); (StatusCode::INTERNAL_SERVER_ERROR, "Couldn't parse track.getInfo!")})?.track; + + Ok(Arc::new((userinfo, trackinfo, trackstub))) + }) +} + +static HTTP: LazyLock = crate::http::lazy(); + +static WHITELIST: LazyLock = LazyLock::new(|| { + let default_cache = Arc::new(RwLock::new(AsyncCache::new(CONFIG.default_refresh, user_getter as UserGetter))); + let whitelist_cache = Arc::new(RwLock::new(AsyncCache::new(CONFIG.whitelist_refresh, user_getter as UserGetter))); + match CONFIG.whitelist_mode.as_str() { + "open" => { + Whitelist::Open{default_cache, whitelist_cache} + }, + "exclusive" => { + if CONFIG.whitelist.is_empty() { + panic!("Exclusive mode set with empty whitelist, cannot serve any requests!"); + } + Whitelist::Exclusive{cache: whitelist_cache} + }, + m => { + panic!("Bad whitelist mode: `{m}`"); + } + } +}); + +pub async fn get_userinfo(user: &String) -> (Result, (StatusCode, &'static str)>, Duration) { + match LazyLock::force(&WHITELIST) { + Whitelist::Open{default_cache, whitelist_cache} => { + if CONFIG.whitelist.contains(user) { + (whitelist_cache.write().await.get_owned(user).await, CONFIG.whitelist_refresh) + } + else { + (default_cache.write().await.get_owned(user).await, CONFIG.default_refresh) + } + }, + Whitelist::Exclusive{cache} => { + if CONFIG.whitelist.contains(user) { + (cache.write().await.get_owned(user).await, CONFIG.whitelist_refresh) + } + else { (Err((StatusCode::FORBIDDEN, "User not in whitelist!")), CONFIG.default_refresh) } + } + } +} diff --git a/src/config.rs b/src/config.rs index 3c11bc8..69328b0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,103 +7,46 @@ use std::time::*; use super::cache::AsyncCache; use super::deserialize::{GetRecentTracks, GetUserInfo, GetTrackInfo, Track, TrackStub, User}; -use super::font::{font_cache, FontCache}; -use reqwest::{Client, StatusCode}; use dotenv::var; use tokio::sync::RwLock; use handlebars::{Handlebars, handlebars_helper}; use duration_str as ds; -type CacheFuture = Pin> + Send + Sync)>>; -type CacheGetter = fn(&String) -> CacheFuture; -type Cache = Arc>>>; - -type UserFuture = CacheFuture>; -type UserGetter = CacheGetter>; -type UserCache = Cache>; - static INTERNAL_THEMES: &[(&str, &str)] = &[("plain", include_str!("themes/plain.hbs"))]; -pub static STATE: LazyLock> = LazyLock::new(|| { - State::new() +pub static CONFIG: LazyLock> = LazyLock::new(|| { + Config::new() }); -fn user_getter(username: &String) -> UserFuture { - let username = urlencoding::encode(username.as_ref()).to_string(); - Box::pin(async move { - let userreq = STATE.http.get(format!("https://ws.audioscrobbler.com/2.0/?method=user.getInfo&format=json&user={username}&api_key={}", STATE.lastfm_api_key)) - .send().await - .map_err(|e| {log::error!("Failed to get info for user `{username}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't connect to last.fm!")})?; - if userreq.status() == StatusCode::NOT_FOUND { return Err((StatusCode::NOT_FOUND, "User does not exist!")); } - - let userinfo = userreq.json::().await - .map_err(|e| {log::error!("Couldn't parse user.getInfo for `{username}`: {e}"); (StatusCode::INTERNAL_SERVER_ERROR, "Couldn't parse user.getInfo!")})?.user; - - let tracksreq = STATE.http.get(format!("https://ws.audioscrobbler.com/2.0/?method=user.getRecentTracks&format=json&extended=1&limit=1&user={username}&api_key={}", STATE.lastfm_api_key)) - .send().await - .map_err(|e| {log::error!("Failed to get tracks for user `{username}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't connect to last.fm!")})?; - if tracksreq.status() == StatusCode::NOT_FOUND { return Err((StatusCode::NOT_FOUND, "User does not exist!")); } - if tracksreq.status() == StatusCode::FORBIDDEN { return Err((StatusCode::FORBIDDEN, "You need to unprivate your song history!")); } - - let trackstub = tracksreq.json::().await - .map_err(|e| {log::error!("Couldn't parse user.getRecentTracks for `{username}`: {e}"); (StatusCode::INTERNAL_SERVER_ERROR, "Couldn't parse user.getRecentTracks!")})? - .recenttracks.track.into_iter().next().ok_or((StatusCode::UNPROCESSABLE_ENTITY, "You need to listen to some songs first!"))?; - - let trackreq = STATE.http.get(format!("https://ws.audioscrobbler.com/2.0/?method=track.getInfo&format=json&username={username}&api_key={}&track={}&artist={}", STATE.lastfm_api_key, trackstub.name, trackstub.artist.name)) - .send().await - .map_err(|e| {log::error!("Failed to get tracks for user `{username}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't connect to last.fm!")})?; - if trackreq.status() == StatusCode::NOT_FOUND { return Err((StatusCode::NOT_FOUND, "Track does not exist!")); } - - let trackinfo = trackreq.json::().await - .map_err(|e| {log::error!("Couldn't parse track.getInfo for `{}` by `{}` on behalf of {username}: {e}", trackstub.name, trackstub.artist.name); (StatusCode::INTERNAL_SERVER_ERROR, "Couldn't parse track.getInfo!")})?.track; - - Ok(Arc::new((userinfo, trackinfo, trackstub))) - }) -} - #[derive(Debug)] -enum Whitelist { - Exclusive{cache: UserCache, whitelist: BTreeSet}, - Open{default_cache: UserCache, whitelist_cache: UserCache, whitelist: BTreeSet} -} -#[derive(Debug)] -pub struct State { - lastfm_api_key: Arc, +pub struct Config { + pub(crate) lastfm_api_key: Arc, port: u16, default_theme: Arc, send_refresh_header: bool, - pub(crate) http: Client, - handlebars: Handlebars<'static>, - pub(crate) google_api_key: Option>, - google_fonts_cache: FontCache, + pub(crate )google_api_key: Option>, - whitelist: Whitelist, - default_refresh: Duration, - whitelist_refresh: Duration, + pub(crate) whitelist: BTreeSet, + pub(crate) whitelist_mode: String, + pub(crate) default_refresh: Duration, + pub(crate) whitelist_refresh: Duration, } -impl State { +impl Config { fn new() -> Arc { let duration_from_var = |v: &str, d: u64| -> Duration {var(v).map(|r| ds::parse(&r).expect("bad duration string")).unwrap_or_else(|_| Duration::from_secs(d))}; - let user_cache_from_duration = |d: Duration| -> UserCache { - Arc::new(RwLock::new(AsyncCache::new(d, user_getter as UserGetter))) - }; let default_refresh = duration_from_var("LFME_DEFAULT_REFRESH", 300); let whitelist_refresh = duration_from_var("LFME_WHITELIST_REFRESH", 60); - let default_cache = user_cache_from_duration(default_refresh); - let whitelist_cache = user_cache_from_duration(whitelist_refresh); - Arc::new(State { + Arc::new(Config { lastfm_api_key: var("LFME_LASTFM_API_KEY").expect("last.fm API key must be set").into(), port: var("LFME_PORT").map(|p| p.parse().expect("cannot parse as a port number")).unwrap_or(9999), default_theme: var("LFME_THEME_DEFAULT").map(Into::into).unwrap_or_else(|_| "plain".into()), send_refresh_header: !var("LFME_NO_REFRESH").map(|h| &h == "1").unwrap_or(false), - http: Client::builder().https_only(true).user_agent(concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"))).build().unwrap(), - handlebars: { let mut hb = Handlebars::new(); handlebars_helper!(url_encode: |s: String| urlencoding::encode(&s)); @@ -125,27 +68,9 @@ impl State { }, google_api_key: var("LFME_GOOGLE_API_KEY").map(Into::into).ok(), - google_fonts_cache: font_cache(), - - whitelist: { - let load_whitelist = || -> Option> { - var("LFME_WHITELIST").ok().map( - |w| w.split(',').map(|s| s.trim().to_string()).collect() - ) - }; - - match var("LFME_WHITELIST_MODE").map(|m| m.to_ascii_lowercase()).unwrap_or_else(|_| "open".into()).as_str() { - "open" => { - Whitelist::Open{default_cache, whitelist_cache, whitelist: load_whitelist().unwrap_or_default()} - }, - "exclusive" => { - Whitelist::Exclusive{cache: whitelist_cache, whitelist: load_whitelist().expect("LFME_WHITELIST not set, unable to serve anyone")} - }, - m => { - panic!("Bad whitelist mode: `{m}`"); - } - } - }, + + whitelist: var("LFME_WHITELIST").ok().map(|w| w.split(',').map(|s| s.trim().to_string()).collect()).unwrap_or_default(), + whitelist_mode: var("LFME_WHITELIST_MODE").map(|m| m.to_ascii_lowercase()).unwrap_or_else(|_| "open".into()), default_refresh: default_refresh + Duration::from_secs(1), whitelist_refresh: whitelist_refresh + Duration::from_secs(1) }) @@ -153,28 +78,7 @@ impl State { pub fn port(&self) -> u16 { self.port } pub fn send_refresh_header(&self) -> bool { self.send_refresh_header } - pub async fn get_fontinfo(&self, font: &String) -> Result, (StatusCode, &'static str)> { - self.google_fonts_cache.write().await.get_owned(font).await - } pub fn has_google_api_key(&self) -> bool { self.google_api_key.is_some() } - pub async fn get_userinfo(&self, user: &String) -> (Result, (StatusCode, &'static str)>, Duration) { - match &self.whitelist { - Whitelist::Open{default_cache, whitelist_cache, whitelist} => { - if whitelist.contains(user) { - (whitelist_cache.write().await.get_owned(user).await, self.whitelist_refresh) - } - else { - (default_cache.write().await.get_owned(user).await, self.default_refresh) - } - }, - Whitelist::Exclusive{cache, whitelist} => { - if whitelist.contains(user) { - (cache.write().await.get_owned(user).await, self.whitelist_refresh) - } - else { (Err((StatusCode::FORBIDDEN, "User not in whitelist!")), self.default_refresh) } - } - } - } pub fn handlebars(&self) -> &Handlebars { &self.handlebars } pub fn default_theme(&self) -> Arc { self.default_theme.clone() } } diff --git a/src/ctx.rs b/src/ctx.rs index 0b250b1..6e0d806 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -2,8 +2,8 @@ use reqwest::StatusCode; use super::deserialize as de; use std::sync::Arc; use std::collections::BTreeMap; -use super::font::FontQuery; -use super::config::STATE; +use crate::cache::font::FontQuery; +use super::config::CONFIG; pub mod model { use std::sync::Arc; @@ -126,7 +126,7 @@ impl ResponseCtx { }, scrobble: model::Scrobble { name: track.name.clone(), - album: track.album.name.clone(), + album: track.album.title.clone(), artist: model::Artist { name: track.artist.name.clone(), // image_url: track.artist.images.iter().max_by(|a, b| a.size.cmp(&b.size)).map(|a| a.url.clone()).unwrap_or_else(|| "".into()), @@ -138,8 +138,8 @@ impl ResponseCtx { loved: track.loved.unwrap_or(false) }, font: match font_query { - Some(FontQuery { google_font: Some(f), .. }) if STATE.has_google_api_key() => { - let css = match STATE.get_fontinfo(&f.to_string()).await { + Some(FontQuery { google_font: Some(f), .. }) if CONFIG.has_google_api_key() => { + let css = match crate::cache::font::get_fontinfo(&f.to_string()).await { Ok(css) => css, Err((status, error)) => { return ResponseCtx(model::Root::Error {error}, status); } }; diff --git a/src/deserialize.rs b/src/deserialize.rs index 9606258..0134ae5 100644 --- a/src/deserialize.rs +++ b/src/deserialize.rs @@ -82,16 +82,15 @@ pub enum ImageSize { #[derive(Deserialize, Debug)] pub struct Image { pub size: ImageSize, - #[serde(rename = "#text")] + #[serde(alias = "#text")] pub url: Arc, } #[derive(Deserialize, Debug)] pub struct Album { - #[serde(rename = "mbid")] - pub uuid: Arc, - #[serde(rename = "#text")] - pub name: Arc, + pub mbid: Option>, + #[serde(alias = "#text")] + pub title: Arc, #[serde(default)] #[serde(rename = "image")] pub images: Vec, @@ -102,9 +101,10 @@ pub struct Album { #[derive(Deserialize, Debug)] pub struct Track { pub artist: Artist, - #[serde(rename = "image")] + #[serde(alias = "image")] + #[serde(default)] pub images: Vec, - pub mbid: Arc, + pub mbid: Option>, pub album: Album, pub name: Arc, pub url: Arc, @@ -138,7 +138,7 @@ pub struct TrackAttr { } #[derive(Deserialize, Debug)] pub struct ArtistStub { - #[serde(rename = "#text")] + #[serde(alias = "#text")] pub name: Arc } #[derive(Deserialize, Debug)] diff --git a/src/font.rs b/src/font.rs deleted file mode 100644 index 6454770..0000000 --- a/src/font.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::sync::Arc; -use std::time::Duration; - -use tokio::sync::RwLock; -use reqwest::StatusCode; - -use super::cache::{CacheFuture, CacheGetter, Cache, AsyncCache}; -use crate::STATE; - -#[derive(serde::Deserialize, Debug, Default)] -#[serde(default)] -#[serde(rename = "kebab-case")] -pub struct FontQuery { - pub font: Option>, - pub include_font: Option>, - pub google_font: Option>, -// pub small_font: Option<()> -} - -pub type FontFuture = CacheFuture>; -pub type FontGetter = CacheGetter>; -pub type FontCache = Cache>; - -fn font_getter(fontname: &String) -> FontFuture { - let fontname = urlencoding::encode(fontname.as_ref()).to_string(); - Box::pin(async move { - let Some(google_api_key) = STATE.google_api_key.clone() - else { - unreachable!(); - }; - - let fontreq = STATE.http.get(format!("https://www.googleapis.com/webfonts/v1/webfonts?key={}&family={fontname}", google_api_key)) - .send().await - .map_err(|e| {log::error!("Failed to get info for font `{fontname}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't connect to Google Fonts!")})?; - if fontreq.status() == StatusCode::NOT_FOUND { return Err((StatusCode::NOT_FOUND, "Font does not exist!")); } - if fontreq.status() == StatusCode::FORBIDDEN { - log::error!("Invalid Google API key in config!"); - return Err((StatusCode::SERVICE_UNAVAILABLE, "This instance is not configured to support Google Fonts properly, please use a different font.")); - } - - let cssreq = STATE.http.get(format!("https://fonts.googleapis.com/css2?family={fontname}")) - .send().await - .map_err(|e| {log::error!("Failed to get CSS for font `{fontname}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't download font CSS!")})?; - - Ok(cssreq.text().await.unwrap().into()) - }) -} - -pub fn font_cache() -> FontCache { - Arc::new(RwLock::new(AsyncCache::new(Duration::from_secs(86400), font_getter as FontGetter))) -} diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..f0a609b --- /dev/null +++ b/src/http.rs @@ -0,0 +1,9 @@ +use std::sync::LazyLock; +use reqwest::Client; + +pub(crate) fn new() -> Client { + Client::builder().https_only(true).user_agent(concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"))).build().unwrap() +} +pub(crate) const fn lazy() -> LazyLock { + LazyLock::new(new) +} diff --git a/src/lib.rs b/src/lib.rs index 8226852..07cd1c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,11 @@ #![feature(lazy_cell)] +mod http; + pub mod deserialize; pub mod cache; pub mod config; pub mod ctx; -pub mod font; -pub use config::STATE; +pub use config::CONFIG; pub use ctx::ResponseCtx; diff --git a/src/main.rs b/src/main.rs index cf7856a..38ee31f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,8 @@ use log::LevelFilter; use std::collections::BTreeMap; use std::fs::File; use std::sync::Arc; -use lfm_embed::{STATE, ResponseCtx}; -use lfm_embed::font::FontQuery; +use lfm_embed::{CONFIG, ResponseCtx}; +use lfm_embed::cache::font::FontQuery; use warp::Filter; #[derive(serde::Deserialize, Debug)] @@ -38,22 +38,20 @@ async fn main() { .unwrap_or(env_logger::Target::Stderr) ).init(); - std::sync::LazyLock::force(&STATE); - let user = warp::path!("user" / String) .and(warp::query::()) .then(|s, q: UserQuery| async move { - log::debug!(target: "lfm::server::user", "Handling request for user `{s}` with {q:?}"); - let (ctx, dur) = STATE.get_userinfo(&s).await; + log::debug!(target: "lfm_embed::server::user", "Handling request for user `{s}` with {q:?}"); + let (ctx, dur) = lfm_embed::cache::user::get_userinfo(&s).await; let ResponseCtx(data, status) = ResponseCtx::create(ctx, q.font, q.rest).await; - let theme = q.theme.filter(|a| STATE.handlebars().has_template(a)).unwrap_or_else(|| STATE.default_theme()); - log::debug!(target: "lfm::server::user", "Using theme {theme}"); + let theme = q.theme.filter(|a| CONFIG.handlebars().has_template(a)).unwrap_or_else(|| CONFIG.default_theme()); + log::debug!(target: "lfm_embed::server::user", "Using theme {theme}"); warp::reply::with_header( warp::reply::with_header( warp::reply::with_status( warp::reply::html( - STATE.handlebars().render(&theme, &data).unwrap() + CONFIG.handlebars().render(&theme, &data).unwrap() ), status ), "Refresh", dur.as_secs() ), "X-Selected-Theme", theme.as_ref() @@ -61,5 +59,5 @@ async fn main() { }); warp::serve(user) - .bind(([127,0,0,1], STATE.port())).await; + .bind(([127,0,0,1], CONFIG.port())).await; } -- cgit v1.2.3-54-g00ecf