From eb59bd978cf33a153e7094f21f9f2aa03182fbe7 Mon Sep 17 00:00:00 2001 From: Mark <> Date: Thu, 21 Aug 2025 23:49:56 +0200 Subject: [PATCH] station data saving, fixes --- index.html | 9 ++++- src/bahn_api/transport_modes.rs | 4 +- src/server/mod.rs | 18 +++++++-- src/storage/mod.rs | 2 + src/storage/stations.rs | 3 ++ src/storage/stations_files.rs | 71 +++++++++++++++++++++++++++++++++ src/storage/stations_saving.rs | 57 ++++++++++++++++++++++++++ stations/.gitkeep | 1 + 8 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 src/storage/stations_files.rs create mode 100644 src/storage/stations_saving.rs create mode 100644 stations/.gitkeep diff --git a/index.html b/index.html index 8c30b3c..487c0f7 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ @@ -22,7 +23,12 @@ body { background: light-dark(#ECE, #000); } +.DraggingTop { + z-index: 15; +} + #Header { + z-index: 5; position: sticky; top: 0px; display: flex; @@ -271,6 +277,7 @@ function draggingOnDown(x, y, e) { if (isInGame) return; if (draggingElement !== null) return; draggingElement = e; + draggingElement.classList.add("DraggingTop"); draggingStartPos = [x, y]; } function draggingOnMove(x, y) { @@ -283,6 +290,7 @@ function draggingOnMove(x, y) { } function draggingOnUp(x, y) { if (draggingElement === null) return; + draggingElement.classList.remove("DraggingTop"); if (!isInGame) { if (draggingEndCallback) draggingEndCallback(x, y); } @@ -558,7 +566,6 @@ async function reloadDepartures() { if (evaNumber === stopEvaNumber) foundCurrent = false; } for (const [stop, evaNumber] of result[0]) { - console.log(stop, ": ", evaNumber); let stopElem = document.createElement("li"); stopElem.innerText = stop; stopElem.style.wordWrap = "nowrap"; diff --git a/src/bahn_api/transport_modes.rs b/src/bahn_api/transport_modes.rs index 5b4aee5..c04c251 100644 --- a/src/bahn_api/transport_modes.rs +++ b/src/bahn_api/transport_modes.rs @@ -1,8 +1,10 @@ use std::fmt::Debug; +use serde::{Deserialize, Serialize}; + pub struct TransportModesSet(u32); -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum TransportMode { Bus, Tram, diff --git a/src/server/mod.rs b/src/server/mod.rs index fb9481f..0e7f20a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,12 +1,12 @@ pub mod ratelimit; -use std::{collections::HashSet, sync::Arc}; +use std::{collections::HashSet, sync::Arc, time::Duration}; use axum::{Json, extract::Path, response::Html, routing::get}; use rand::seq::IndexedRandom; use ratelimit::Ratelimit; use reqwest::StatusCode; -use tokio::sync::Mutex; +use tokio::{sync::Mutex, time::sleep}; use crate::{ bahn_api::{ @@ -28,7 +28,19 @@ pub async fn main() { let ratelimit = Ratelimit::new(10, 10); - let stations = Arc::new(Mutex::new(Stations::new())); + let stations = Arc::new(Mutex::new(Stations::load().await.unwrap().unwrap())); + { + let stations = Arc::clone(&stations); + tokio::task::spawn(async move { + loop { + #[cfg(debug_assertions)] + sleep(Duration::from_secs(30)).await; + #[cfg(not(debug_assertions))] + sleep(Duration::from_secs(900)).await; + stations.lock().await.save().await; + } + }); + } axum::serve( tokio::net::TcpListener::bind( diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 7acd88c..524dae6 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1 +1,3 @@ pub mod stations; +pub mod stations_files; +pub mod stations_saving; diff --git a/src/storage/stations.rs b/src/storage/stations.rs index 175f6a4..ceae2f1 100644 --- a/src/storage/stations.rs +++ b/src/storage/stations.rs @@ -60,6 +60,9 @@ impl Stations { self.stations.insert(eva_number, station); } } + pub(super) fn insert_raw(&mut self, eva_number: StationEvaNumber, station: Station) { + self.stations.insert(eva_number, station); + } pub fn get(&self, eva_number: &StationEvaNumber) -> Option<&Station> { self.stations.get(eva_number) diff --git a/src/storage/stations_files.rs b/src/storage/stations_files.rs new file mode 100644 index 0000000..f61e2db --- /dev/null +++ b/src/storage/stations_files.rs @@ -0,0 +1,71 @@ +use serde::{Deserialize, Serialize}; + +use crate::bahn_api::{ + basic_types::station_ids::{DbStopId, StationEvaNumber}, + transport_modes::{TransportMode, TransportModesSet}, +}; + +use super::stations::Station; + +#[derive(Serialize)] +struct StationJsonRef<'a> { + name: &'a str, + db_station_id: &'a str, + related_stations: Vec<&'a str>, + lat_lon: Option<(f64, f64)>, + transport_modes: Vec, +} +#[derive(Deserialize)] +struct StationJson { + name: String, + db_station_id: String, + related_stations: Vec, + lat_lon: Option<(f64, f64)>, + transport_modes: Vec, +} + +pub fn station_to_string( + Station { + name, + db_station_id, + related_stations, + lat_lon, + transport_modes, + has_been_changed: _, + }: &Station, +) -> String { + serde_json::to_string(&StationJsonRef { + name: &name, + db_station_id: &db_station_id.0, + related_stations: related_stations.iter().map(|v| v.0.as_str()).collect(), + lat_lon: *lat_lon, + transport_modes: transport_modes + .as_ref() + .map(|v| v.iter().collect()) + .unwrap_or_else(Vec::default), + }) + .unwrap() +} + +pub fn station_from_string(string: &str) -> Result { + let StationJson { + name, + db_station_id, + related_stations, + lat_lon, + transport_modes, + } = serde_json::from_str(string)?; + Ok(Station { + name, + db_station_id: DbStopId(db_station_id), + related_stations: related_stations.into_iter().map(StationEvaNumber).collect(), + lat_lon, + transport_modes: Some( + transport_modes + .into_iter() + .fold(TransportModesSet::new(), |set, m| set.with(m)), + ) + .filter(|set| set.len() > 0), + has_been_changed: false, + }) +} diff --git a/src/storage/stations_saving.rs b/src/storage/stations_saving.rs new file mode 100644 index 0000000..732671a --- /dev/null +++ b/src/storage/stations_saving.rs @@ -0,0 +1,57 @@ +use crate::bahn_api::basic_types::station_ids::StationEvaNumber; + +use super::{ + stations::Stations, + stations_files::{station_from_string, station_to_string}, +}; + +impl Stations { + pub async fn save(&mut self) { + for (key, station) in self.stations.iter_mut() { + if station.has_been_changed { + eprintln!("Saving station {:?} ({:?}).", station.name, key.0); + station.has_been_changed = false; + if key.0.chars().all(|ch| ch.is_ascii_alphanumeric()) { + if let Err(e) = tokio::fs::write( + format!("stations/{}", key.0.as_str()), + station_to_string(station), + ) + .await + { + eprintln!( + "[stations/save] could not save station {:?}: {e}.", + station.name + ); + } + } else { + eprintln!( + "[stations/save] skipping station {:?} because key {:?} contained at least one non-ascii-alphanumeric character.", + station.name, key.0 + ); + } + } + } + } + + pub async fn load() -> Result, tokio::io::Error> { + let mut dir_entries = tokio::fs::read_dir("stations/").await?; + let mut s = Self::new(); + while let Some(file) = dir_entries.next_entry().await? { + if let Some(name) = file + .file_name() + .to_str() + .filter(|name| name.chars().all(|ch| ch.is_ascii_alphanumeric())) + { + let eva_number = StationEvaNumber(name.to_owned()); + let mut station = + match station_from_string(&tokio::fs::read_to_string(file.path()).await?) { + Ok(v) => v, + Err(e) => return Ok(Err(e)), + }; + station.has_been_changed = false; + s.insert_raw(eva_number, station); + } + } + Ok(Ok(s)) + } +} diff --git a/stations/.gitkeep b/stations/.gitkeep new file mode 100644 index 0000000..eb03da7 --- /dev/null +++ b/stations/.gitkeep @@ -0,0 +1 @@ +station data goes here, program will not start without this folder