81 lines
3.7 KiB
Python
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()
|