from __future__ import annotations from pathlib import Path from typing import Any from .fsutil import sha256_file SCAN_DIRS = ("Plugins", "Libs", "IPA/Pending") BOOTSTRAP_DIRS = ("Libs", "IPA") BOOTSTRAP_ROOT_GLOBS = ("winhttp.dll", "IPA.exe*") def scan_instance(instance_path: Path, include_hashes: bool = False) -> dict[str, Any]: files: list[dict[str, Any]] = [] for dirname in SCAN_DIRS: root = instance_path / dirname if not root.exists(): continue for path in sorted(item for item in root.rglob("*") if item.is_file()): rel = path.relative_to(instance_path).as_posix() entry: dict[str, Any] = {"path": rel, "size": path.stat().st_size} if include_hashes: entry["sha256"] = sha256_file(path) files.append(entry) return { "instancePath": str(instance_path), "files": files, "counts": { "files": len(files), "plugins": sum(1 for item in files if item["path"].startswith("Plugins/")), "libs": sum(1 for item in files if item["path"].startswith("Libs/")), "pending": sum(1 for item in files if item["path"].startswith("IPA/Pending/")), }, } def scan_bootstrap_files(instance_path: Path) -> list[dict[str, Any]]: files_by_path: dict[str, dict[str, Any]] = {} for pattern in BOOTSTRAP_ROOT_GLOBS: for path in sorted(instance_path.glob(pattern)): if not path.is_file(): continue rel = path.relative_to(instance_path).as_posix() files_by_path[rel] = { "path": rel, "size": path.stat().st_size, "sha256": sha256_file(path), } for dirname in BOOTSTRAP_DIRS: root = instance_path / dirname if not root.exists(): continue for path in sorted(item for item in root.rglob("*") if item.is_file()): rel = path.relative_to(instance_path).as_posix() files_by_path[rel] = { "path": rel, "size": path.stat().st_size, "sha256": sha256_file(path), } return [files_by_path[path] for path in sorted(files_by_path)]