feat: set the save folder from the options

This commit is contained in:
Abdessamad Derraz committed 2026-10-24 03:59:21 +02:00
1 parent d24adf9592
commit 3797b482d2
3 files changed
+78 -8

No files matched your search

+39 -4
View File
@@ -10843,6 +10843,10 @@ def _option_rows(state: TuiState) -> list[TuiRow]:
chosen = f"{len(state.cores)} chosen" if state.cores else "all"
rows.append(TuiRow("cores", f"Cores: {chosen}", "--core"))
copied = ", ".join(str(folder) for folder in state.from_folders) or "none"
if manifest.get("save_directory"):
# The interface said "Name it with --save-dir" and had no row for it.
rows.append(TuiRow("savedir", f"Save folder: {_save_folder_words(state)}",
"--save-dir", style="path"))
rows += [
TuiRow("jobs", f"Parallel downloads: {state.jobs}", "--jobs"),
TuiRow("folder", f"BIOS folder: {_short_path(state.dest, state.home)}", "--dest",
@@ -10854,6 +10858,21 @@ def _option_rows(state: TuiState) -> list[TuiRow]:
return rows
def _save_dir_given(state: TuiState) -> Path | None:
return next((Path(value) for flag, value in state.passthrough
if flag == "--save-dir" and value), None)
def _save_folder_words(state: TuiState) -> str:
"""The frontend's save directory as the Options row shows it."""
given = _save_dir_given(state)
if given is not None:
return _short_path(given, state.home)
if state.saves and state.saves.get("folder"):
return _short_path(Path(state.saves["folder"]), state.home)
return "not known"
def _push(state: TuiState, screen: str, focus: int = 0) -> list[tuple]:
state.stack.append(screen)
state.focus[screen] = focus
@@ -11178,6 +11197,12 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]:
state.path_selected = bool(state.path_text)
return _push(state, "path")
if screen == "browse":
if action == "use" and state.browse_for == "save":
state.passthrough = [flag for flag in state.passthrough
if flag[0] != "--save-dir"]
state.passthrough.append(("--save-dir", str(state.browse_dir)))
state.browse_for = "dest"
return _start_check(state)
if action in ("use", "zip") and state.browse_for == "from":
chosen = state.browse_dir if action == "use" else row.value
if chosen not in state.from_folders:
@@ -11293,6 +11318,10 @@ def _option(state: TuiState, action: str, step: int) -> list[tuple]:
state.status = f"No longer copying from {_last_name(str(removed))}."
elif action == "from":
return _open_from_browser(state)
elif action == "savedir":
state.browse_for = "save"
_push(state, "browse")
_browse_to(state, _nearest_folder(_save_dir_given(state) or state.home))
elif action == "preview":
_push(state, "preview")
return _start_run(state, "preview")
@@ -12036,7 +12065,8 @@ def _plan_lines(check: dict, dest: Path | None, state: TuiState) -> list:
if check.get("not_installable"):
lines += _unavailable_lines(state)
if check is state.check:
lines += _unplaced_lines(state.check_summary, state)
lines += _unplaced_lines(state.check_summary, state,
"Choose it in Options, Save folder.")
return lines[:2] + offline_lines + lines[2:]
@@ -12053,7 +12083,8 @@ def _saves_lines(state: TuiState, saves: dict | None) -> list[str]:
f"{_short_path(Path(saves['folder']), state.home)}"]
def _unplaced_lines(summary: dict | None, state: TuiState) -> list[str]:
def _unplaced_lines(summary: dict | None, state: TuiState,
remedy: str = "Name it with --save-dir.") -> 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 []
@@ -12062,7 +12093,7 @@ def _unplaced_lines(summary: dict | None, state: TuiState) -> list[str]:
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."]
f"its place is not known. {remedy}"]
def _view_setup(state: TuiState, width: int, height: int) -> tuple:
@@ -12123,7 +12154,11 @@ def _view_browse(state: TuiState, width: int, height: int) -> tuple:
if state.browse_for == "from":
after.insert(0, "Choose the old card, a backup folder or a ZIP: its BIOS files "
"are copied, each one checked, nothing on it is changed.")
return ("Copy from" if state.browse_for == "from" else "Browse for a folder",
elif state.browse_for == "save":
after.insert(0, f"Choose {display_name(state.platform)}'s save directory: some "
"cores read files there.")
return ("Copy from" if state.browse_for == "from"
else "Save folder" if state.browse_for == "save" else "Browse for a folder",
_compose(state, width, height, [where], tui_rows(state), after),
("A/Enter Open", "B/Esc Back", "? Help"))
+37 -3
View File
@@ -2719,13 +2719,47 @@ class AuditRoundFour(unittest.TestCase):
"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.")
"not known. ")
plan = " ".join(" ".join(install.tui_view(state, 120, 40)).split())
self.assertIn(line, plan)
# The plan names the row that sets it, the result the option a
# command carries.
self.assertIn(line + "Choose it in Options, Save folder.", plan)
state.stack.append("result")
state.code = 0
state.summary = {**summary, "mode": "install", "installed": 5}
self.assertIn(line, install.tui_outcome(state)[1])
self.assertIn(line + "Name it with --save-dir.", install.tui_outcome(state)[1])
class TheSaveFolderHasARow(unittest.TestCase):
"""A card on a PC left the save-directory files out and named
--save-dir, which Options had no row for: the interface user had to
leave it and type a command."""
def state(self) -> install.TuiState:
state = checked_state()
state.manifest = {**MANIFEST, "save_directory": {"folder": "saves", "by_core": True}}
return state
def test_options_list_it_and_the_browser_sets_it(self):
state = self.state()
rows = install._option_rows(state)
[row] = [row for row in rows if row.action == "savedir"]
self.assertEqual((row.label, row.right), ("Save folder: not known", "--save-dir"))
saves = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, saves, True)
state.stack = ["setup", "plan", "options"]
install._option(state, "savedir", 0)
self.assertEqual(state.screen, "browse")
self.assertIn("save directory", " ".join(" ".join(install.tui_view(state, 120, 40))
.split()))
install._browse_to(state, saves)
effects = install._activate(state, install.TuiRow("use", "Use this folder"))
self.assertIn(("--save-dir", str(saves)), install.tui_flags(state))
self.assertTrue(any(effect[0] == "run" for effect in effects), effects)
self.assertEqual(install._save_folder_words(state), str(saves))
def test_a_list_without_a_save_directory_has_no_row(self):
self.assertNotIn("savedir", [row.action for row in install._option_rows(checked_state())])
class OfflineInTheInterface(unittest.TestCase):
+2 -1
View File
@@ -133,7 +133,8 @@ much space is free, before anything is written.
The other choices sit one press away. **Choose systems** ticks the consoles you
play, with the total to download under the list. **Options** sets the
preferred regions, game data, the installed cores only, the cores, the parallel
downloads and the folder. Each row names the option it sets, and **Show the
downloads, the folder and, for a list with files read from the frontend's save
directory, that directory. Each row names the option it sets, and **Show the
command line** gives the same choice as one command for a script. **Preview the
file list** shows what would be downloaded and writes nothing. **Copy the BIOS
files from your old card or a folder** opens the folder browser, mounted cards