Files
hockey_new/hockey_data/snapshot_storage.py

52 lines
2.1 KiB
Python

"""Bound debug reports and reuse identical XML without breaking historical paths."""
from __future__ import annotations
import hashlib
import os
import re
import tempfile
import time
from pathlib import Path
def save_xml_snapshot(directory: Path, content: bytes, *, resource: str, tournament: str = "") -> Path:
directory.mkdir(parents=True, exist_ok=True)
safe_resource = re.sub(r"[^A-Za-z0-9_-]", "_", str(resource))[:64] or "resource"
safe_tournament = re.sub(r"[^A-Za-z0-9_-]", "_", str(tournament))[:48]
digest = hashlib.sha256(content).hexdigest()
path = directory / f"{digest}_{safe_tournament}_{safe_resource}.xml"
if path.is_file() and not path.is_symlink() and path.read_bytes() == content:
return path
temporary = None
try:
with tempfile.NamedTemporaryFile(dir=directory, prefix="snapshot_", suffix=".tmp", delete=False) as stream:
temporary = Path(stream.name)
stream.write(content)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, path)
finally:
if temporary is not None:
temporary.unlink(missing_ok=True)
return path
def trim_discovery_reports(directory: Path, *, keep: int = 40, days: int = 30) -> int:
"""Only standalone debug JSON is disposable; XML paths may be stored in PostgreSQL."""
pattern = re.compile(r"^\d{8}_\d{6}_\d{6}_[A-Za-z0-9_-]+_games_discovery\.json$")
reports = sorted((path for path in directory.glob("*_games_discovery.json")
if pattern.fullmatch(path.name) and path.is_file() and not path.is_symlink()),
key=lambda path: path.name, reverse=True)
cutoff = time.time() - max(1, days) * 86400
removed = 0
for path in reports[max(1, keep):]:
try:
if path.stat().st_mtime < cutoff:
path.unlink(missing_ok=True)
removed += 1
except OSError:
# Concurrent maintenance or a read-only diagnostic directory must
# not turn a successful data import into a failed synchronization.
continue
return removed