From f8fa1918cd06522be850c049267e6eb2b2f70b90 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 11:29:06 +0200 Subject: [PATCH] =?UTF-8?q?Quellen=20ohne=20Last-Modified=20(Wiener=20Lini?= =?UTF-8?q?en):=20t=C3=A4glich=20laden=20und=20am=20Inhalt=20erkennen,=20o?= =?UTF-8?q?b=20es=20Neues=20gibt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb --- trainpulse/config.py | 1 + trainpulse/prepare.py | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/trainpulse/config.py b/trainpulse/config.py index 595014a..d98ae39 100644 --- a/trainpulse/config.py +++ b/trainpulse/config.py @@ -77,6 +77,7 @@ SOURCES = { }, "at_wl": { # Wiener Linien (Tram, U-Bahn, Badner Bahn), CC BY 4.0 "country": "at", + "check_hours": 24, # Server nennt kein Datum: 1x täglich laden (~100 MB) und Inhalt vergleichen "gtfs_url": "http://www.wienerlinien.at/ogd_realtime/doku/ogd/gtfs/gtfs.zip", "rt_url": "", "rt_key": "", "needs_key": False, "rt_interval": 60, }, diff --git a/trainpulse/prepare.py b/trainpulse/prepare.py index 1cbb936..0768a57 100644 --- a/trainpulse/prepare.py +++ b/trainpulse/prepare.py @@ -8,6 +8,7 @@ und macht für eine Quelle nichts, solange dort kein neuer Fahrplan veröffentli """ import argparse import csv +import hashlib import datetime as dt import io import json @@ -69,14 +70,15 @@ def download(url: str, dest: Path) -> None: def remote_id(src: str, url: str) -> tuple[str, str | None]: - """Kennung der aktuell veröffentlichten Datei, ohne sie herunterzuladen: (Name, Datum JJJJMMTT oder None).""" + """Kennung der aktuell veröffentlichten Datei, ohne sie herunterzuladen: (Name, Datum JJJJMMTT oder None). + Name None: der Server sagt nichts (Wiener Linien) – dann entscheidet der Inhalt nach dem Download.""" if src == "ch": return latest_gtfs_name(url), None r = httpx.head(url, follow_redirects=True, timeout=60, headers={"User-Agent": C.USER_AGENT}) r.raise_for_status() lm = r.headers.get("last-modified") if not lm: - raise RuntimeError(f"{url}: kein Last-Modified-Header") + return None, dt.date.today().strftime("%Y%m%d") date = dt.datetime.strptime(lm, "%a, %d %b %Y %H:%M:%S GMT").strftime("%Y%m%d") return f"{src}-{lm}", date @@ -347,12 +349,18 @@ def prepare(source: str, state: dict, zip_path: Path | None = None, force: bool state[source + "@checked"] = time.time() url = gtfs_url(source) name, version = remote_id(source, url) - if f"{name} #{FORMAT}" == state.get(source) and not force: + if name and f"{name} #{FORMAT}" == state.get(source) and not force: log.info("[%s] Kein neuer Fahrplan (%s)", source, name) return zip_path = work / "gtfs.zip" - log.info("[%s] Lade Fahrplan %s", source, name) + log.info("[%s] Lade Fahrplan %s", source, name or "(ohne Datum vom Server)") download(url, zip_path) + if not name: # ohne Datum vom Server: am Inhalt erkennen, ob es Neues gibt + with open(zip_path, "rb") as f: + name = f"{source}-{hashlib.file_digest(f, 'sha256').hexdigest()[:16]}" + if f"{name} #{FORMAT}" == state.get(source) and not force: + log.info("[%s] Kein neuer Fahrplan (gleicher Inhalt)", source) + return t = time.time() version = version or zip_path.stem # (nach dem Glätten hiesse die Datei sonst "normalized")