station data saving, fixes

This commit is contained in:
Mark 2025-08-21 23:49:56 +02:00
parent bb0ddb40f1
commit eb59bd978c
8 changed files with 160 additions and 5 deletions

View File

@ -3,6 +3,7 @@
<!--
IDEAS:
- Achievements (i.e. "take a train which is delayed by at least 60 minutes" xd)
- Bonus bei Verspätung, Gesamtverspätung sammeln
- Show a map in the end (no need for world map, just use coordinates on flat black rectangle)
-->
<head>
@ -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";

View File

@ -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,

View File

@ -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(

View File

@ -1 +1,3 @@
pub mod stations;
pub mod stations_files;
pub mod stations_saving;

View File

@ -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)

View File

@ -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<TransportMode>,
}
#[derive(Deserialize)]
struct StationJson {
name: String,
db_station_id: String,
related_stations: Vec<String>,
lat_lon: Option<(f64, f64)>,
transport_modes: Vec<TransportMode>,
}
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<Station, serde_json::Error> {
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,
})
}

View File

@ -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<Result<Self, serde_json::Error>, 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))
}
}

1
stations/.gitkeep Normal file
View File

@ -0,0 +1 @@
station data goes here, program will not start without this folder