mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 12:49:03 -05:00
fix: find the save directory for a named folder
A run given --dest detected nothing, so retroarch.cfg was never read: the full screen, which names every folder, sent Azahar's keys.txt to a guessed RetroArch/saves or dropped it without a word, where line mode wrote it to savefile_directory. The configs are now read once when a save directory is asked for, and the plan and the result say what was left out.
This commit is contained in:
1 parent
ccc8af7a47
commit
abba99d352
4 files changed
+140
-3
No files matched your search
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
$defaultInstallSha256 = "2103f380bac5e2dc6ab91183283d268cccce81971da89f2963bab752d1518ef2"
|
||||
$defaultInstallSha256 = "7065a9d52aa9016066c273ec8a5907b09fcce7eaad5a423583c79515f09edb99"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installerExit = 0
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
|
||||
+42
-1
@@ -3949,6 +3949,27 @@ def _cfg_true(value: str | None) -> bool:
|
||||
return (value or "").strip().lower() == "true"
|
||||
|
||||
|
||||
_CONFIGS_READ: list[bool] = []
|
||||
|
||||
|
||||
def _retroarch_config_for(bios_path: Path) -> tuple[Path, Path] | None:
|
||||
"""The retroarch.cfg whose system_directory is bios_path, and the
|
||||
folder ':' expands to there.
|
||||
|
||||
A run given --dest detects nothing, so the configs are read here, once
|
||||
and silently, the first time a save directory is asked for: the
|
||||
interface names every folder with --dest, and its runs sent Azahar's
|
||||
keys.txt to a guessed folder where line mode read savefile_directory.
|
||||
"""
|
||||
found = _RETROARCH_CONFIGS.get(bios_path)
|
||||
if found is not None or _CONFIGS_READ:
|
||||
return found
|
||||
_CONFIGS_READ.append(True)
|
||||
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
|
||||
detect_platforms(_INVOCATION.os_type or detect_os())
|
||||
return _RETROARCH_CONFIGS.get(bios_path)
|
||||
|
||||
|
||||
def save_directory_for(
|
||||
bios_path: Path, manifest: dict, override: Path | None = None,
|
||||
) -> SavePlace | str:
|
||||
@@ -3975,7 +3996,7 @@ def save_directory_for(
|
||||
folder = layout["folder"]
|
||||
listed = bool(layout["by_core"])
|
||||
by_core = listed
|
||||
found = None if layout.get("by_system") else _RETROARCH_CONFIGS.get(bios_path)
|
||||
found = None if layout.get("by_system") else _retroarch_config_for(bios_path)
|
||||
if found is not None:
|
||||
cfg_path, app_dir = found
|
||||
for key in ("savefiles_in_content_dir", "sort_savefiles_by_content_enable"):
|
||||
@@ -9920,6 +9941,8 @@ class TuiState:
|
||||
self.dry: dict | None = None
|
||||
self.dry_files: list = []
|
||||
self.dry_summary: dict | None = None
|
||||
# The check run's summary: what it left out and why.
|
||||
self.check_summary: dict | None = None
|
||||
# The confirm screen waits for A even when the plan is unchanged:
|
||||
# it was opened by Space, which never installs by itself.
|
||||
self.must_confirm: bool = False
|
||||
@@ -10427,6 +10450,7 @@ def _start_run(state: TuiState, run: str) -> list[tuple]:
|
||||
# dropped the --system the command line gave.
|
||||
state.check, state.files, state.systems, state.reading = None, [], [], None
|
||||
state.found, state.scanned, state.unreadable = [], [], []
|
||||
state.check_summary = None
|
||||
state.checked_flags = tui_flags(state)
|
||||
elif run in ("confirm", "preview"):
|
||||
state.dry, state.dry_files, state.dry_summary = None, [], None
|
||||
@@ -11060,6 +11084,8 @@ def _take_event(state: TuiState, event: dict) -> None:
|
||||
state.files.append(event)
|
||||
elif kind == "system":
|
||||
state.systems.append(event)
|
||||
elif kind == "summary":
|
||||
state.check_summary = event
|
||||
elif state.run in ("confirm", "preview"):
|
||||
if kind == "check":
|
||||
state.dry = event
|
||||
@@ -11413,9 +11439,23 @@ def _plan_lines(check: dict, dest: Path | None, state: TuiState) -> list:
|
||||
lines += _scan_lines(state)
|
||||
if check.get("not_installable"):
|
||||
lines += _unavailable_lines(state)
|
||||
if check is state.check:
|
||||
lines += _unplaced_lines(state.check_summary, state)
|
||||
return lines
|
||||
|
||||
|
||||
def _unplaced_lines(summary: dict | None, state: TuiState) -> list[str]:
|
||||
"""The files read from the frontend's save directory that the run left
|
||||
out, that directory being unknown: said, never silent."""
|
||||
count = sum(group.get("files", 0) for group in (summary or {}).get("skipped") or []
|
||||
if group.get("reason") == "no_save_directory")
|
||||
if not count:
|
||||
return []
|
||||
name = display_name(state.platform) if state.platform else "the frontend"
|
||||
return [f"{_plural(count, 'file')} read from {name}'s save directory left out: "
|
||||
"its place is not known. Name it with --save-dir."]
|
||||
|
||||
|
||||
def _view_setup(state: TuiState, width: int, height: int) -> tuple:
|
||||
rows = tui_rows(state)
|
||||
focus = rows[min(state.focus.get("setup", 0), len(rows) - 1)]
|
||||
@@ -11895,6 +11935,7 @@ def tui_outcome(state: TuiState) -> tuple[str, list[str]]:
|
||||
lines += _unavailable_lines(state)
|
||||
for group in summary.get("left_out") or []:
|
||||
lines.append(f"Left out as {group['label']}: {_plural(group['files'], 'file')}.")
|
||||
lines += _unplaced_lines(summary, state)
|
||||
lines += ["Same choice without questions:", f" {tui_command(state)}"]
|
||||
if tui_keeps_log(state):
|
||||
lines.append(f"Details: {state.log}")
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ esac
|
||||
TEMP_INSTALLER=""
|
||||
TEMP_DIRECTORY=""
|
||||
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
DEFAULT_INSTALL_SHA256="2103f380bac5e2dc6ab91183283d268cccce81971da89f2963bab752d1518ef2"
|
||||
DEFAULT_INSTALL_SHA256="7065a9d52aa9016066c273ec8a5907b09fcce7eaad5a423583c79515f09edb99"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -2525,6 +2525,36 @@ class AuditRoundThree(unittest.TestCase):
|
||||
sorted(path.name for path in (running, failed, unknown, other)))
|
||||
|
||||
|
||||
class AuditRoundFour(unittest.TestCase):
|
||||
"""tui_audit_3.md, each finding held where it was found."""
|
||||
|
||||
UNPLACED = {"reason": "no_save_directory", "label": "no save directory", "files": 2,
|
||||
"required": 0}
|
||||
|
||||
def test_p1_1_files_left_out_of_the_save_directory_are_said(self):
|
||||
state = fresh_state()
|
||||
state.stack = ["setup"]
|
||||
with mock.patch.object(install.Path, "exists", return_value=True):
|
||||
install.tui_update(state, Key("enter"))
|
||||
install.tui_receive(state, ("manifest", "retroarch", MANIFEST))
|
||||
summary = {"event": "summary", "mode": "check", "installed": 0, "up_to_date": 0,
|
||||
"pending": 5, "failed": [], "skipped": [self.UNPLACED], "left_out": [],
|
||||
"kept": [], "interrupted": False}
|
||||
for event in [*FILES, *SYSTEMS, CHECK, summary]:
|
||||
install.tui_receive(state, ("event", state.run_id, event))
|
||||
with mock.patch.object(install, "_tui_folder_facts", return_value={
|
||||
"free": 50 * 1024 ** 3, "writable": True, "cores": []}):
|
||||
install.tui_receive(state, ("ended", state.run_id, 1, []))
|
||||
line = ("2 files read from RetroArch's save directory left out: its place is "
|
||||
"not known. Name it with --save-dir.")
|
||||
plan = " ".join(" ".join(install.tui_view(state, 120, 40)).split())
|
||||
self.assertIn(line, plan)
|
||||
state.stack.append("result")
|
||||
state.code = 0
|
||||
state.summary = {**summary, "mode": "install", "installed": 5}
|
||||
self.assertIn(line, install.tui_outcome(state)[1])
|
||||
|
||||
|
||||
class Crash(unittest.TestCase):
|
||||
"""An unexpected error ends on one sentence and a log, never a traceback."""
|
||||
|
||||
@@ -2684,6 +2714,29 @@ class GameDataRepository(FakeRepository):
|
||||
return self
|
||||
|
||||
|
||||
class SaveDirectoryRepository(FakeRepository):
|
||||
"""The fake repository with one file read from RetroArch's save
|
||||
directory, as Azahar's keys.txt and 105 others of the real list."""
|
||||
|
||||
KEYS = b"azahar keys"
|
||||
|
||||
def __enter__(self) -> SaveDirectoryRepository:
|
||||
super().__enter__()
|
||||
path = self.root / "install" / "retroarch.json"
|
||||
manifest = json.loads(path.read_text())
|
||||
(self.root / "bios" / "keys.txt").write_bytes(self.KEYS)
|
||||
manifest["files"].append({
|
||||
"dest": "Azahar/Azahar/sysdata/keys.txt", "root": "save",
|
||||
"core_folder": "Azahar", "sha1": hashlib.sha1(self.KEYS).hexdigest(),
|
||||
"sha256": hashlib.sha256(self.KEYS).hexdigest(), "size": len(self.KEYS),
|
||||
"repo_path": "bios/keys.txt", "cores": ["azahar"],
|
||||
"systems": ["nintendo-3ds"]})
|
||||
manifest["save_directory"] = {"folder": "saves", "by_core": True}
|
||||
manifest["base_destination"] = "system"
|
||||
path.write_text(json.dumps(manifest))
|
||||
return self
|
||||
|
||||
|
||||
@unittest.skipUnless(pyte and pty and hasattr(pty, "fork"),
|
||||
"screen tests need pyte (pip install pyte==0.8.2) and a pty")
|
||||
class EndToEnd(unittest.TestCase):
|
||||
@@ -2735,6 +2788,49 @@ class EndToEnd(unittest.TestCase):
|
||||
# The cursor and the main screen come back before the result is printed.
|
||||
self.assertIn(b"\x1b[?25h\x1b[?1049l", term.raw)
|
||||
|
||||
def test_the_save_directory_is_the_one_line_mode_finds(self):
|
||||
"""The interface named the BIOS folder with --dest, so its runs never
|
||||
read retroarch.cfg: keys.txt went to a guessed RetroArch/saves, or
|
||||
was dropped without a word, where line mode writes it to the
|
||||
savefile_directory RetroArch reads."""
|
||||
import subprocess
|
||||
|
||||
def written(repo, saves: Path) -> list[str]:
|
||||
return sorted([*repo.installed(), *(
|
||||
"saves/" + p.relative_to(saves).as_posix()
|
||||
for p in saves.rglob("*") if p.is_file())])
|
||||
|
||||
results = []
|
||||
for mode in ("line", "interface"):
|
||||
with self.subTest(mode=mode):
|
||||
repo = self.repo(SaveDirectoryRepository)
|
||||
home = Path(self.env["HOME"])
|
||||
saves = home / "MySaves"
|
||||
saves.mkdir()
|
||||
cfg = home / ".config" / "retroarch" / "retroarch.cfg"
|
||||
cfg.write_text(f'system_directory = "{repo.dest}"\n'
|
||||
f'savefile_directory = "{saves}"\n')
|
||||
if mode == "line":
|
||||
proc = subprocess.run(
|
||||
[sys.executable, str(REPO_ROOT / "install.py"), "--platform",
|
||||
"retroarch", "--yes"], env=self.env, capture_output=True,
|
||||
text=True, timeout=60, check=False, stdin=subprocess.DEVNULL)
|
||||
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||
self.assertIn("6 files installed", proc.stdout)
|
||||
else:
|
||||
term = self.start()
|
||||
term.wait("Where do you play?")
|
||||
term.send("\r")
|
||||
screen = term.wait("files to download")
|
||||
self.assertIn("6 files to download", screen)
|
||||
term.send("\r")
|
||||
term.wait("6 files installed.")
|
||||
term.send("\r")
|
||||
self.assertEqual(term.close(), 0)
|
||||
results.append(written(repo, saves))
|
||||
self.assertIn("saves/Azahar/Azahar/sysdata/keys.txt", results[-1])
|
||||
self.assertEqual(results[0], results[1])
|
||||
|
||||
def test_one_system_from_the_checklist(self):
|
||||
repo = self.repo()
|
||||
term = self.start("--platform", "retroarch", "--dest", str(repo.dest), cols=40, rows=15)
|
||||
|
||||
Reference in new issue
Block a user