52 lines
2.1 KiB
Python
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
|