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:
FURRO404
2026-05-13 23:17:02 -07:00
commit 2b399fdb81
186 changed files with 96596 additions and 0 deletions
+124
View File
@@ -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())