This commit is contained in:
@@ -95,6 +95,10 @@ class SteamCmdBatchResult:
|
||||
failed_ids: set[str]
|
||||
output_tail: str
|
||||
|
||||
@property
|
||||
def has_io_failure(self) -> bool:
|
||||
return "i/o operation failed" in self.output_tail.lower()
|
||||
|
||||
|
||||
class SteamCmdRunner:
|
||||
def __init__(
|
||||
@@ -106,6 +110,16 @@ class SteamCmdRunner:
|
||||
self.executable = executable
|
||||
self.content_dir = content_dir
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self._io_recovery_attempted = False
|
||||
|
||||
@property
|
||||
def install_root(self) -> Path:
|
||||
try:
|
||||
return self.content_dir.parents[3]
|
||||
except IndexError as error:
|
||||
raise ValueError(
|
||||
f"Invalid SteamCMD Workshop content path: {self.content_dir}"
|
||||
) from error
|
||||
|
||||
def executable_path(self) -> str | None:
|
||||
configured = Path(self.executable)
|
||||
@@ -130,6 +144,39 @@ class SteamCmdRunner:
|
||||
"Check the container volume ownership."
|
||||
) from error
|
||||
|
||||
def recover_io_failure(self) -> list[Path]:
|
||||
"""Quarantine Steam's transient Workshop state without touching mods."""
|
||||
if self._io_recovery_attempted:
|
||||
return []
|
||||
self._io_recovery_attempted = True
|
||||
workshop_dir = self.content_dir.parent.parent
|
||||
candidates = [
|
||||
workshop_dir / f"appworkshop_{RIMWORLD_APP_ID}.acf",
|
||||
workshop_dir / "downloads" / RIMWORLD_APP_ID,
|
||||
workshop_dir / "temp" / RIMWORLD_APP_ID,
|
||||
]
|
||||
existing = [candidate for candidate in candidates if candidate.exists()]
|
||||
if not existing:
|
||||
return []
|
||||
|
||||
timestamp = (
|
||||
datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
||||
+ f"-{time.time_ns()}"
|
||||
)
|
||||
recovery_dir = workshop_dir / ".progression-recovery" / timestamp
|
||||
recovery_dir.mkdir(parents=True, exist_ok=True)
|
||||
moved: list[Path] = []
|
||||
for candidate in existing:
|
||||
label = (
|
||||
candidate.name
|
||||
if candidate.is_file()
|
||||
else f"{candidate.parent.name}-{candidate.name}"
|
||||
)
|
||||
destination = recovery_dir / label
|
||||
shutil.move(str(candidate), str(destination))
|
||||
moved.append(destination)
|
||||
return moved
|
||||
|
||||
def run_batch(
|
||||
self,
|
||||
workshop_ids: list[str],
|
||||
@@ -145,7 +192,13 @@ class SteamCmdRunner:
|
||||
raise ValueError(f"Invalid Workshop ids: {', '.join(invalid)}")
|
||||
self.ensure_workshop_writable()
|
||||
|
||||
command = [executable, "+login", "anonymous"]
|
||||
command = [
|
||||
executable,
|
||||
"+force_install_dir",
|
||||
str(self.install_root),
|
||||
"+login",
|
||||
"anonymous",
|
||||
]
|
||||
for workshop_id in workshop_ids:
|
||||
command.extend(
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user