Files
dj/model-bridge/restore_local_data.py

81 lines
3.7 KiB
Python

"""Recover the existing project snapshot and its original server outputs.
Never overwrites an existing target database or regenerates media.
"""
from concurrent.futures import ThreadPoolExecutor
import hashlib
import json
import os
from pathlib import Path
import sqlite3
import requests
ROOT = Path(__file__).resolve().parent.parent
TARGET = ROOT / "huobao-data"
SOURCE = ROOT / "integration-work/backups/huobao-before-multivideo-20261106.sqlite3"
def main():
TARGET.mkdir(parents=True, exist_ok=True)
dest = TARGET / "huobao.sqlite3"
if dest.exists():
raise RuntimeError("Recovery target already exists; inspect it instead of overwriting")
empty = Path(os.environ["APPDATA"]) / "HuobaoDrama/data/huobao.sqlite3"
snapshot = ROOT / "integration-work/backups/huobao-empty-profile-20261106.sqlite3"
if empty.exists() and not snapshot.exists():
with sqlite3.connect(empty.as_uri() + "?mode=ro", uri=True) as a, sqlite3.connect(snapshot) as b:
a.backup(b)
with sqlite3.connect(SOURCE.as_uri() + "?mode=ro", uri=True) as a, sqlite3.connect(dest) as b:
a.backup(b)
with sqlite3.connect(dest) as conn:
assert conn.execute("pragma integrity_check").fetchone()[0] == "ok"
jobs = conn.execute("select id,task_id,local_path from sys_task where type='image' and status='completed' and local_path is not null").fetchall()
client = json.loads((ROOT / "model-bridge/client.json").read_text("utf-8"))
def download(job):
task_id, remote_id, relative = job
target = (TARGET / relative.lstrip("/")).resolve()
target.relative_to(TARGET.resolve())
session = requests.Session()
session.trust_env = False
response = session.get(client["url"] + "/v1/images/task/" + remote_id,
headers={"Authorization": "Bearer " + client["token"]}, timeout=30)
if response.status_code != 200:
return {"task_id": task_id, "error": "Status query failed", "status": response.status_code}
payload = response.json()
if payload.get("status") != "completed":
return {"task_id": task_id, "error": "Original image is unavailable"}
url = payload["image_url"]
if not url.startswith(client["url"] + "/media/"):
raise RuntimeError("Unexpected media destination")
with session.get(url, stream=True, timeout=120) as media:
if media.status_code != 200:
return {"task_id": task_id, "error": "Download failed", "status": media.status_code}
target.parent.mkdir(parents=True, exist_ok=True)
partial = target.with_name(target.name + ".partial")
digest = hashlib.sha256()
count = 0
with partial.open("wb") as out:
for data in media.iter_content(1024 * 1024):
out.write(data)
digest.update(data)
count += len(data)
partial.replace(target)
return {"task_id": task_id, "path": relative, "bytes": count, "sha256": digest.hexdigest()}
with ThreadPoolExecutor(max_workers=3) as pool:
outcomes = list(pool.map(download, jobs))
report = {"source": str(SOURCE), "target": str(TARGET), "images": outcomes}
(ROOT / "model-bridge/data-recovery-20261106.json").write_text(json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8")
failed = [r for r in outcomes if "error" in r]
print("Recovered image files:", len(outcomes) - len(failed), "/", len(outcomes))
if failed:
print("Unrecovered task IDs:", [r["task_id"] for r in failed])
raise RuntimeError("Some images need additional recovery")
print("Project database integrity: OK")
if __name__ == "__main__":
main()