change to hex and update DBs (#1284)

This commit is contained in:
NotSoToothless
2026-05-29 07:05:20 -07:00
committed by GitHub
parent 66e010c890
commit 6fb303e26f
5 changed files with 543 additions and 44 deletions
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""
migrate_replay_ids.py
One-shot migration: renames decimal replay directories to hex and updates
the _id field inside each replay_data.json.gz to match.
Run on the server:
python3 migrate_replay_ids.py
python3 migrate_replay_ids.py --dry-run # preview without touching anything
Reads STORAGE_VOL_PATH from environment or TSSBOT/.env.
"""
from __future__ import annotations
import argparse
import gzip
import json
import os
import sys
from pathlib import Path
_HERE = Path(__file__).resolve().parent
# Try loading .env if python-dotenv is available
try:
from dotenv import load_dotenv
load_dotenv(dotenv_path=_HERE / ".env")
except ImportError:
pass
STORAGE_VOL_PATH = os.environ.get("STORAGE_VOL_PATH", "").strip()
if not STORAGE_VOL_PATH:
print("ERROR: STORAGE_VOL_PATH not set", file=sys.stderr)
sys.exit(1)
REPLAYS_DIR = Path(STORAGE_VOL_PATH) / "REPLAYS" / "TSS"
def is_decimal_id(name: str) -> bool:
"""True if the directory name looks like a plain decimal integer (not hex)."""
try:
int(name)
return True
except ValueError:
return False
def to_hex(decimal_str: str) -> str:
return hex(int(decimal_str))[2:].lower()
def migrate_one(src_dir: Path, *, dry_run: bool) -> tuple[str, str] | None:
"""
Rename src_dir from decimal to hex, update _id inside replay_data.json.gz.
Returns (old_name, new_name) on success, None if already hex or skipped.
"""
name = src_dir.name
if not is_decimal_id(name):
return None
hex_name = to_hex(name)
dst_dir = src_dir.parent / hex_name
if dst_dir.exists():
print(f" SKIP {name}{hex_name} (destination already exists)")
return None
gz_path = src_dir / "replay_data.json.gz"
if gz_path.exists():
try:
with gzip.open(gz_path, "rb") as fh:
data = json.loads(fh.read().decode("utf-8"))
except Exception as exc:
print(f" ERROR reading {gz_path}: {exc}")
return None
data["_id"] = hex_name
if not dry_run:
with gzip.open(gz_path, "wb") as fh:
fh.write(json.dumps(data, ensure_ascii=False).encode("utf-8"))
if not dry_run:
src_dir.rename(dst_dir)
return name, hex_name
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--dry-run", action="store_true", help="Print what would happen without changing anything")
args = parser.parse_args()
if not REPLAYS_DIR.is_dir():
print(f"ERROR: {REPLAYS_DIR} does not exist", file=sys.stderr)
sys.exit(1)
entries = sorted(p for p in REPLAYS_DIR.iterdir() if p.is_dir())
candidates = [p for p in entries if is_decimal_id(p.name)]
if not candidates:
print("No decimal-named replay directories found. Nothing to do.")
return
mode = "DRY RUN — " if args.dry_run else ""
print(f"{mode}Found {len(candidates)} decimal director(ies) to migrate in {REPLAYS_DIR}\n")
ok = skipped = errors = 0
for src_dir in candidates:
result = migrate_one(src_dir, dry_run=args.dry_run)
if result is None:
skipped += 1
else:
old, new = result
tag = "[dry-run] " if args.dry_run else ""
print(f" {tag}{old}{new}")
ok += 1
print(f"\nDone. migrated={ok} skipped={skipped} errors={errors}")
if args.dry_run:
print("(dry run — nothing was changed)")
if __name__ == "__main__":
main()