Headers, cache tweaks
This commit is contained in:
@@ -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! {
|
||||
|
||||
Reference in New Issue
Block a user