Files
seismo-relay/sfm/db_snapshot.py
T

51 lines
1.9 KiB
Python

"""
db_snapshot.py — WAL-safe snapshot / restore helpers for seismo_relay.db and
the waveform store, backing the /db/snapshot, /db/waveforms/recent.zip and
/db/restore endpoints in server.py. Kept out of server.py so the logic is
unit-testable without HTTP.
"""
from __future__ import annotations
import sqlite3
import zipfile
from pathlib import Path
def snapshot_db(src_path: Path, dest_path: Path) -> Path:
"""WAL-safe copy of a live SQLite DB via the SQLite online backup API.
Consistent even while the source is being written under WAL, and never
blocks the source's writers."""
src = sqlite3.connect(str(src_path))
dest = sqlite3.connect(str(dest_path))
try:
with dest:
src.backup(dest)
finally:
src.close()
dest.close()
return Path(dest_path)
def build_recent_waveforms_zip(db, store, n: int, dest_path: Path) -> dict:
"""Zip every on-disk file (event file + .h5/.a5.pkl/.sfm.json siblings) for
the n most-recent events, arcname '<serial>/<filename>'. Events with no
resolvable files on disk are skipped. Returns {'events': int, 'files': int}."""
rows = db.query_events(limit=n, offset=0)
events = 0
files = 0
with zipfile.ZipFile(dest_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for row in rows:
serial = row.get("serial")
fn = row.get("blastware_filename")
if not serial or not fn:
continue
serial_dir = store.root / serial
matched = sorted(serial_dir.glob(f"{fn}*")) if serial_dir.is_dir() else []
matched = [p for p in matched if p.is_file()]
if not matched:
continue
events += 1
for p in matched:
zf.write(p, arcname=f"{serial}/{p.name}")
files += 1
return {"events": events, "files": files}