Add BeatMods parsing and userdata restore
This commit is contained in:
@@ -84,6 +84,19 @@ def infer_windows_appdata_path(instance_path: Path) -> Path:
|
||||
return profile / "AppData" / "LocalLow" / "Hyperbolic Magnetism" / "Beat Saber"
|
||||
|
||||
|
||||
def infer_proton_appdata_path() -> Path:
|
||||
return (
|
||||
Path.home()
|
||||
/ ".local/share/BSManager/SharedContent/compatdata/pfx/drive_c/users/steamuser/AppData/LocalLow/Hyperbolic Magnetism/Beat Saber"
|
||||
)
|
||||
|
||||
|
||||
def infer_appdata_path(instance_path: Path) -> Path:
|
||||
if "Users" in instance_path.resolve().parts:
|
||||
return infer_windows_appdata_path(instance_path)
|
||||
return infer_proton_appdata_path()
|
||||
|
||||
|
||||
def sync_windows_data_repo(
|
||||
*,
|
||||
instance: str,
|
||||
@@ -96,7 +109,7 @@ def sync_windows_data_repo(
|
||||
("UserData", instance_path / "UserData", backup_root / "UserData"),
|
||||
]
|
||||
if include_appdata:
|
||||
appdata = appdata_path or infer_windows_appdata_path(instance_path)
|
||||
appdata = appdata_path or infer_appdata_path(instance_path)
|
||||
sources.append(("AppData", appdata, backup_root / "AppData"))
|
||||
|
||||
for label, source, _ in sources:
|
||||
@@ -145,6 +158,63 @@ def sync_windows_data_repo(
|
||||
}
|
||||
|
||||
|
||||
def restore_windows_data_repo(
|
||||
*,
|
||||
instance: str,
|
||||
instance_path: Path,
|
||||
backup_root: Path,
|
||||
appdata_path: Path | None = None,
|
||||
include_appdata: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
descriptor_path = backup_root / "backup-descriptor.json"
|
||||
if descriptor_path.is_file():
|
||||
descriptor = json.loads(descriptor_path.read_text(encoding="utf-8"))
|
||||
descriptor_instance = descriptor.get("instance")
|
||||
if descriptor_instance and descriptor_instance != instance:
|
||||
raise ValueError(
|
||||
f"backup descriptor instance {descriptor_instance!r} does not match requested instance {instance!r}"
|
||||
)
|
||||
|
||||
created_at = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
||||
restores: list[tuple[str, Path, Path]] = [
|
||||
("UserData", backup_root / "UserData", instance_path / "UserData"),
|
||||
]
|
||||
if include_appdata:
|
||||
appdata = appdata_path or infer_appdata_path(instance_path)
|
||||
restores.append(("AppData", backup_root / "AppData", appdata))
|
||||
|
||||
for label, source, _ in restores:
|
||||
if not source.is_dir():
|
||||
raise FileNotFoundError(f"{label} backup not found: {source}")
|
||||
|
||||
restored: list[dict[str, Any]] = []
|
||||
snapshots: list[dict[str, Any]] = []
|
||||
for label, source, destination in restores:
|
||||
snapshot: Path | None = None
|
||||
if destination.exists():
|
||||
snapshot = destination.parent / f"{destination.name}.pre-restore-{created_at}"
|
||||
destination.rename(snapshot)
|
||||
snapshots.append({"label": label, "path": str(snapshot)})
|
||||
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copytree(source, destination, symlinks=True)
|
||||
restored.append(
|
||||
{
|
||||
"label": label,
|
||||
"source": str(source),
|
||||
"destination": str(destination),
|
||||
"fileCount": sum(1 for item in destination.rglob("*") if item.is_file()),
|
||||
"snapshot": str(snapshot) if snapshot else None,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"backupRoot": str(backup_root),
|
||||
"restored": restored,
|
||||
"snapshots": snapshots,
|
||||
}
|
||||
|
||||
|
||||
def _ignore_backup_paths(source_root: Path, skipped: list[str]) -> Callable[[str, list[str]], set[str]]:
|
||||
def ignore(current_dir: str, names: list[str]) -> set[str]:
|
||||
ignored: set[str] = set()
|
||||
|
||||
Reference in New Issue
Block a user