add SREBOT, SHARED, TSSBOT contents (fixup for #1223)
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>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user