"""Check/import a local collection. Rejected games never get queued. python -m app.import_library /games --check python -m app.import_library /games --import --owner admin """ import argparse import asyncio import json from pathlib import Path import tempfile import sys from app.converter.archive import ArchiveError, extract from app.converter.compatibility import CompatibilityError, Issue from app.converter.preflight import inspect_archive, inspect_directory ARCHIVES = (".zip", ".7z", ".rar", ".tar", ".tar.gz", ".tgz", ".tar.bz2", ".tar.xz") def entries(root: Path): """An unpacked copy takes precedence over an archive with the identical name.""" directories = {p.name.casefold() for p in root.iterdir() if p.is_dir() and not p.name.startswith(".")} for source in sorted(root.iterdir(), key=lambda p: p.name.casefold()): if source.name.startswith(".") or source.is_symlink(): continue suffix = next((ext for ext in ARCHIVES if source.name.lower().endswith(ext)), None) if source.is_file() and suffix and source.name[:-len(suffix)].casefold() in directories: continue yield source def check(source: Path) -> dict: try: if source.is_dir(): return inspect_directory(source) if not source.name.lower().endswith(ARCHIVES): if source.suffix.lower() == ".exe" and source.stat().st_size >= 4: with source.open("rb") as handle: handle.seek(-4, 2) if handle.read(4) == b"GDPC": raise CompatibilityError("godot", [Issue("unsupported_engine", "В EXE обнаружен встроенный пакет Godot; его исполнение пока не поддерживается")]) raise CompatibilityError("unknown", [Issue("not_distribution", "Нужна папка или архив полного дистрибутива; отдельные EXE и RPA не импортируются")]) return inspect_archive(source, source.name) except CompatibilityError as exc: return exc.report() except Exception as exc: return {"compatible": False, "engine": "unknown", "error": str(exc), "issues": [{"code": "input_error", "message": str(exc), "location": source.name, "count": 1}]} async def import_one(source: Path, report: dict, username: str | None) -> dict: from sqlalchemy import select from app.db import SessionLocal from app.models import Game, GameVersion, Role, User from app.worker import convert async with SessionLocal() as db: stmt = select(User).join(Role).order_by(User.id) stmt = stmt.where(User.username == username) if username else stmt.where(Role.name == "admin") owner = await db.scalar(stmt.limit(1)) if owner is None: raise ValueError("Пользователь для импорта не найден") game = Game(title=source.name, engine=report["engine"], created_by_id=owner.id) db.add(game) await db.flush() version = GameVersion(game_id=game.id, owner_id=owner.id, name="Оригинал", upload_name=source.name, status="importing", report=report) db.add(version) await db.commit() game_id, version_id = game.id, version.id ok = await convert(version_id, source, source.name) return {"gameId": game_id, "versionId": version_id, "status": "ready" if ok else "failed"} async def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("root", type=Path) mode = parser.add_mutually_exclusive_group(required=True) mode.add_argument("--check", action="store_true", help="Только проверка; без изменения библиотеки") mode.add_argument("--import", dest="import_games", action="store_true", help="Импортировать совместимые игры") parser.add_argument("--owner") parser.add_argument("--output", type=Path, help="Записать полный JSON-отчёт") args = parser.parse_args() if not args.root.is_dir(): parser.error("root должен быть каталогом коллекции") results = [] for source in entries(args.root): print(json.dumps({"game": source.name, "status": "checking"}, ensure_ascii=False), flush=True) report = await asyncio.to_thread(check, source) result = {"game": source.name, **report} if args.import_games and report.get("compatible"): result.update(await import_one(source, report, args.owner)) results.append(result) print(json.dumps(result, ensure_ascii=False), flush=True) if args.output: args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8") if any(not result.get("compatible") or result.get("status") == "failed" for result in results): raise SystemExit(2) if __name__ == "__main__": sys.stdout.reconfigure(encoding="utf-8") asyncio.run(main())