mod api; mod cache; use std::{collections::BTreeMap, sync::Arc, time::Duration}; use cache::Cache; use rocket::{get, response::content::RawHtml, routes, State}; use tokio::time::Instant; #[get("/")] fn index(cache: &State>) -> RawHtml<&str> { RawHtml(&cache.index) } #[get("/favicon.ico")] fn favicon() -> &'static [u8] { include_bytes!("favicon.ico") } #[get("/uni/all")] async fn uni_all(cache: &State>) -> RawHtml { let seconds_until_next_update = cache.update_swu().await.as_secs() + 1; let data = cache.data.lock().await; let mut o = format!("#{}#{}", data.seq, seconds_until_next_update); for departure in data.departures.iter() { if let Some(route) = &departure.route_name { o.push_str("\nR"); o.push_str(route); } if let Some(dir) = &departure.departure_direction_text { o.push_str("\nD"); o.push_str(dir); } if let Some(dir) = departure.direction { o.push_str(&format!("\nd{dir}")); } if let Some(vehicle) = departure.vehicle_number { o.push_str(&format!("\nv{vehicle}")); } if let Some(time) = &departure.departure_time { o.push_str("\nt"); o.push_str(&format!("{time}")); } if let Some(time) = &departure.scheduled_time { o.push_str("\ns"); o.push_str(&format!("{time}")); } o.push_str("\n-"); // v.route_name } RawHtml(o) } #[get("/bf/all")] async fn bf_all(cache: &State>) -> String { cache.update_bahn().await; cache.bahnhof_html.lock().await.clone() } #[get("/nojs")] async fn nojs(cache: &State>) -> RawHtml { let mut html = cache.nojs.clone(); html.push_str("\n"); cache.update_swu().await; let data = cache.data.lock().await; let mut routes = BTreeMap::>::new(); #[derive(PartialEq, Eq)] struct StrOrderedByLen<'a>(&'a str); impl<'a> std::cmp::PartialOrd for StrOrderedByLen<'a> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl<'a> std::cmp::Ord for StrOrderedByLen<'a> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.0 .len() .cmp(&other.0.len()) .then_with(|| self.0.cmp(other.0)) } } for departure in data.departures.iter() { if let Some(route_name) = &departure.route_name { if let Some(route) = routes.get_mut(&StrOrderedByLen(route_name.as_str())) { route.push(departure); } else { routes.insert(StrOrderedByLen(route_name.as_str()), vec![departure]); } } } let now = chrono::Local::now().naive_local() + Duration::from_secs(10); for (route_name, departures) in routes { html.push_str("

Linie "); html_escape::encode_safe_to_string(&route_name.0, &mut html); html.push_str("

\n"); for departure in departures { html.push_str(r#"
"#); html_escape::encode_safe_to_string( departure .departure_direction_text .as_ref() .map(|v| v.as_str()) .unwrap_or("[?direction?]"), &mut html, ); html.push_str(r#" in now { let remaining_time = time.signed_duration_since(now); let mins = remaining_time.num_minutes(); let secs = remaining_time.num_seconds() % 60; let soon = match (mins, secs) { (0, _) | (1, 0) => "1min", (1, _) | (2, 0) => "2min", (2..=4, _) | (5, 0) => "5min", (5..=9, _) | (10, 0) => "10min", (10..=14, _) | (15, 0) => "15min", (15.., _) => "future", (..=-1, _) => "now", }; if mins > 0 { (soon, format!("{}m{}s ({})", mins, secs, time.time())) } else { ( soon, format!("{}s ({})", remaining_time.num_seconds(), time.time()), ) } } else { ("now", format!("now ({})", time.time())) } }); html.push_str(time.as_ref().map(|v| v.0).unwrap_or("unknown")); html.push_str(r#"">"#); html_escape::encode_safe_to_string( time.map(|v| v.1).unwrap_or("[?time?]".to_owned()), &mut html, ); html.push_str("
\n"); } } drop(data); html.push_str("\n"); html.push_str("\n"); RawHtml(html) } #[rocket::main] async fn main() -> Result<(), rocket::Error> { let cache = Arc::new(Cache::new( std::fs::read_to_string("index.html") .ok() .unwrap_or_else(|| include_str!("index.html").to_owned()), std::fs::read_to_string("nojs.html") .ok() .unwrap_or_else(|| include_str!("nojs.html").to_owned()), std::env::args().nth(1).map(|v| { v.parse() .expect("argument must be an integer (Haltestelle als SWU Stop-Number)") }), )); let rocket = rocket::build() .manage(Arc::clone(&cache)) .mount("/", routes![index, nojs, favicon, uni_all, bf_all]) .ignite() .await?; let _direction_updater_task = tokio::task::spawn(async move { let start = Instant::now(); while start.elapsed().as_secs() < 120 * 60 { tokio::time::sleep(Duration::from_secs(15)).await; tokio::time::sleep(cache.update_swu().await).await; } }); rocket.launch().await?; Ok(()) }