Headers, cache tweaks

This commit is contained in:
2025-11-05 08:46:33 -08:00
parent e9863707d8
commit 063ea165d1
8 changed files with 455 additions and 132 deletions

View File

@@ -1,8 +1,13 @@
use std::time::Instant;
use std::{
pin::Pin,
sync::Arc,
time::{Duration, Instant},
};
use assetserver::Asset;
use chrono::TimeDelta;
use chrono::{DateTime, TimeDelta, Utc};
use maud::{Markup, PreEscaped, html};
use parking_lot::Mutex;
use serde::Deserialize;
use tracing::{debug, warn};
@@ -11,7 +16,7 @@ use crate::{
md::Markdown,
misc::{Backlinks, FarLink},
},
page::{Page, PageMetadata},
page::{DeviceType, Page, PageMetadata, RequestContext},
routes::assets::Image_Icon,
};
@@ -23,6 +28,66 @@ struct HandoutEntry {
solutions: Option<String>,
}
struct CachedRequestInner<T> {
last_fetch: DateTime<Utc>,
last_value: Option<Arc<T>>,
}
pub struct CachedRequest<T> {
inner: Mutex<CachedRequestInner<T>>,
ttl: TimeDelta,
get: Box<dyn Fn() -> Pin<Box<dyn Future<Output = T> + Send + Sync>> + Send + Sync>,
}
impl<T> CachedRequest<T> {
pub fn new(
ttl: TimeDelta,
get: Box<dyn Fn() -> Pin<Box<dyn Future<Output = T> + Send + Sync>> + Send + Sync>,
) -> Arc<Self> {
Arc::new(Self {
get,
ttl,
inner: Mutex::new(CachedRequestInner {
last_fetch: Utc::now(),
last_value: None,
}),
})
}
pub async fn get(self: Arc<Self>) -> Arc<T> {
let now = Utc::now();
let expires = self.inner.lock().last_fetch + self.ttl;
if now < expires
&& let Some(last_value) = self.inner.lock().last_value.clone()
{
return last_value;
}
let res = Arc::new((self.get)().await);
let mut inner = self.inner.lock();
inner.last_fetch = now;
inner.last_value = Some(res.clone());
return res;
}
pub async fn autoget(self: Arc<Self>, interval: Duration) {
loop {
{
let now = Utc::now();
let res = Arc::new((self.get)().await);
let mut inner = self.inner.lock();
inner.last_fetch = now;
inner.last_value = Some(res.clone());
}
tokio::time::sleep(interval).await;
}
}
}
async fn get_index() -> Result<Vec<HandoutEntry>, reqwest::Error> {
let start = Instant::now();
let res = reqwest::get(
@@ -49,28 +114,59 @@ async fn get_index() -> Result<Vec<HandoutEntry>, reqwest::Error> {
return Ok(res);
}
fn build_list_for_group(handouts: &[HandoutEntry], group: &str) -> Markup {
html! {
ul class="handout-ul" {
fn build_list_for_group(
handouts: &[HandoutEntry],
group: &str,
req_ctx: &RequestContext,
) -> Markup {
let mobile = req_ctx.client_info.device_type == DeviceType::Mobile;
@for h in handouts {
@if h.group ==group {
li {
span class="handdout-li-title" {
strong { (h.title) }
}
" "
span class="handout-li-links" {
"[ "
if mobile {
html! {
ul class="handout-ul" {
@for h in handouts {
@if h.group ==group {
li {
span class="handout-li-title" {
a href=(h.handout) class="underline-link" {
strong style="text-decoration: underline;text-underline-offset:1.5pt;color:var(--fgColor);" { (h.title) }
}
}
@if let Some(solutions) = &h.solutions {
a href=(h.handout) {"handout"}
" | "
a href=(solutions) {"solutions"}
} @else {
a href=(h.handout) {"handout"}
" ["
a href=(solutions) { "sols" }
"]"
}
}
}
}
}
}
} else {
html! {
ul class="handout-ul" {
@for h in handouts {
@if h.group ==group {
li {
span class="handout-li-title" {
strong { (h.title) }
}
" "
span class="handout-li-links" {
"[ "
@if let Some(solutions) = &h.solutions {
a href=(h.handout) {"handout"}
" | "
a href=(solutions) {"solutions"}
} @else {
a href=(h.handout) {"handout"}
}
" ]"
}
" ]"
}
}
}
@@ -86,6 +182,13 @@ fn build_list_for_group(handouts: &[HandoutEntry], group: &str) -> Markup {
pub fn handouts() -> Page {
let md = Markdown::parse(include_str!("handouts.md"));
let index = CachedRequest::new(
TimeDelta::minutes(30),
Box::new(|| Box::pin(async move { get_index().await })),
);
tokio::spawn(index.clone().autoget(Duration::from_secs(60 * 20)));
#[expect(clippy::unwrap_used)]
let mut meta = PageMetadata::from_markdown_frontmatter(&md)
.unwrap()
@@ -101,44 +204,36 @@ pub fn handouts() -> Page {
meta,
html_ttl: Some(TimeDelta::seconds(300)),
generate_html: Box::new(move |page| {
generate_html: Box::new(move |page, req_ctx| {
let html = html.clone(); // TODO: find a way to not clone here
let index = index.clone();
Box::pin(async move {
let handouts = get_index().await;
let handouts = index.get().await;
let warmups = match &handouts {
Ok(handouts) => build_list_for_group(handouts, "Warm-Ups"),
let fallback = html! {
span style="color:var(--yellow)" {
"Could not load handouts, something broke."
}
" "
(
FarLink(
"https://git.betalupi.com/Mark/-/packages/generic/ormc-handouts/latest",
"Try this direct link."
)
)
};
let warmups = match &*handouts {
Ok(handouts) => build_list_for_group(handouts, "Warm-Ups", req_ctx),
Err(error) => {
warn!("Could not load handout index: {error:?}");
html! {
span style="color:var(--yellow)" {
"Could not load handouts, something broke."
}
" "
(
FarLink(
"https://git.betalupi.com/Mark/-/packages/generic/ormc-handouts/latest",
"Try this direct link."
)
)
}
fallback.clone()
}
};
let advanced = match &handouts {
Ok(handouts) => build_list_for_group(handouts, "Advanced"),
Err(_) => html! {
span style="color:var(--yellow)" {
"Could not load handouts, something broke."
}
" "
(
FarLink(
"https://git.betalupi.com/Mark/-/packages/generic/ormc-handouts/latest",
"Try this direct link."
)
)
},
let advanced = match &*handouts {
Ok(handouts) => build_list_for_group(handouts, "Advanced", req_ctx),
Err(_) => fallback,
};
html! {

View File

@@ -22,7 +22,7 @@ pub fn index() -> Page {
slug: None,
},
generate_html: Box::new(move |_page| {
generate_html: Box::new(move |_page, _| {
Box::pin(async {
html! {
h2 id="about" { "About" }