2b399fdb81
PR #1223 only staged the deletions of the old paths because the new top-level directories were still untracked when the commit was authored. This commit adds the actual restructured tree: SREBOT/ (existing bot), SHARED/ (vromfs, data_parser, ICONS/MAPS/FONTS, DAGOR_FILES, update_game_files), and TSSBOT/ (skeleton). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
125 lines
3.9 KiB
Python
125 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Move legacy repo-root replays into STORAGE/REPLAYS.
|
|
|
|
Legacy directories were named replays/0<hex_session_id>. The new canonical
|
|
layout is STORAGE/REPLAYS/<hex_session_id>.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
DEFAULT_SOURCE = REPO_ROOT / "replays"
|
|
_storage_env = os.environ.get("SREBOT_STORAGE_VOL_PATH", "").strip()
|
|
if not _storage_env:
|
|
raise RuntimeError("SREBOT_STORAGE_VOL_PATH must be set")
|
|
DEFAULT_STORAGE = Path(_storage_env)
|
|
|
|
LEGACY_REPLAY_DIR = re.compile(r"^0([0-9a-fA-F]+)$")
|
|
HEX_REPLAY_DIR = re.compile(r"^[0-9a-fA-F]+$")
|
|
|
|
|
|
def canonical_name(name: str) -> str | None:
|
|
legacy = LEGACY_REPLAY_DIR.fullmatch(name)
|
|
if legacy:
|
|
return legacy.group(1).lower()
|
|
if HEX_REPLAY_DIR.fullmatch(name):
|
|
return name.lower()
|
|
return None
|
|
|
|
|
|
def iter_files(root: Path) -> list[Path]:
|
|
return [p for p in root.rglob("*") if p.is_file()]
|
|
|
|
|
|
def copy_replay_dir(source: Path, dest: Path, dry_run: bool) -> tuple[int, int]:
|
|
copied = 0
|
|
skipped = 0
|
|
for src_file in iter_files(source):
|
|
rel = src_file.relative_to(source)
|
|
dst_file = dest / rel
|
|
if dst_file.exists() and dst_file.stat().st_size == src_file.stat().st_size:
|
|
skipped += 1
|
|
continue
|
|
copied += 1
|
|
if dry_run:
|
|
continue
|
|
dst_file.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(src_file, dst_file)
|
|
return copied, skipped
|
|
|
|
|
|
def copied_completely(source: Path, dest: Path) -> bool:
|
|
for src_file in iter_files(source):
|
|
dst_file = dest / src_file.relative_to(source)
|
|
if not dst_file.exists() or dst_file.stat().st_size != src_file.stat().st_size:
|
|
return False
|
|
return True
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--source", type=Path, default=DEFAULT_SOURCE, help="Legacy replays directory.")
|
|
parser.add_argument(
|
|
"--storage-dir",
|
|
type=Path,
|
|
default=DEFAULT_STORAGE,
|
|
help="Storage root. Destination defaults to <storage-dir>/REPLAYS.",
|
|
)
|
|
parser.add_argument("--dest", type=Path, help="Destination replay directory.")
|
|
parser.add_argument("--execute", action="store_true", help="Actually copy files. Default is dry-run.")
|
|
parser.add_argument("--move", action="store_true", help="Remove source dirs after a successful executed copy.")
|
|
args = parser.parse_args()
|
|
|
|
source = args.source.expanduser().resolve()
|
|
dest_root = (args.dest or (args.storage_dir / "REPLAYS")).expanduser().resolve()
|
|
dry_run = not args.execute
|
|
|
|
if args.move and dry_run:
|
|
parser.error("--move requires --execute")
|
|
if not source.exists():
|
|
raise SystemExit(f"Source does not exist: {source}")
|
|
|
|
planned = 0
|
|
copied_total = 0
|
|
skipped_total = 0
|
|
|
|
print(f"Source: {source}")
|
|
print(f"Destination: {dest_root}")
|
|
print(f"Mode: {'dry-run' if dry_run else 'execute'}")
|
|
|
|
for entry in sorted(source.iterdir()):
|
|
if not entry.is_dir():
|
|
continue
|
|
new_name = canonical_name(entry.name)
|
|
if new_name is None:
|
|
print(f"skip non-replay dir: {entry.name}")
|
|
continue
|
|
|
|
planned += 1
|
|
dest = dest_root / new_name
|
|
copied, skipped = copy_replay_dir(entry, dest, dry_run)
|
|
copied_total += copied
|
|
skipped_total += skipped
|
|
print(f"{entry.name} -> {new_name}: copy {copied}, skip {skipped}")
|
|
|
|
if args.move and copied_completely(entry, dest):
|
|
shutil.rmtree(entry)
|
|
print(f"removed source dir: {entry}")
|
|
|
|
print(f"Replay dirs: {planned}; files to copy: {copied_total}; already present: {skipped_total}")
|
|
if dry_run:
|
|
print("Dry-run only. Re-run with --execute to copy files.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|