diff --git a/install.py b/install.py index f73c0d7f4..57a39c40b 100644 --- a/install.py +++ b/install.py @@ -10304,6 +10304,11 @@ class TuiState: # A --dest given without --platform: the list asks whose files go there. self.dest_given: Path | None = None self.folder_default: Path | None = None + # What folder_default is, for the words above it: "usual" (the + # platform's built-in folder), "found" (the one detected here), + # "chosen" (the run's folder, from Change folder) or "closed" (the + # one chosen could not be opened, none is offered). + self.folder_kind: str = "usual" self.path_text: str = "" self.browse_dir: Path | None = None self.browse_rows: list = [] @@ -10954,7 +10959,7 @@ def _choose(state: TuiState, plat: str, dest: Path) -> list[tuple]: # The folder found here is the one the frontend reads: the built-in # default beside it is one it does not, offered as "reads its BIOS # files from" and filled with Enter. - state.folder_default = None + state.folder_default, state.folder_kind = None, "closed" return _push(state, "folder") card = card_system(dest) if card and card[0] == plat and card[1] != dest: @@ -10979,9 +10984,14 @@ def _choose(state: TuiState, plat: str, dest: Path) -> list[tuple]: return _start_check(state) -def _open_folder_screen(state: TuiState, default: Path | None) -> list[tuple]: - state.folder_default = default - return _push(state, "folder") +def _open_folder_screen(state: TuiState, default: Path | None, + kind: str = "usual") -> list[tuple]: + state.folder_default, state.folder_kind = default, kind + refused = kind == "chosen" and (state.facts.get("writable") is False + or _short_of_space(state, state.check or {})) + # A folder just refused as full or read-only: Browse, not Use this + # folder, is under A, or A, A came back to the same warning. + return _push(state, "folder", 1 if refused and default is not None else 0) def _mount_name(mount: Path) -> str: @@ -11115,8 +11125,15 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]: if state.dest_given is not None: return _choose(state, row.value, state.dest_given) state.platform = row.value - default = None if row.value in CARD_PLATFORMS else _default_dest(state.os_type, row.value) - return _open_folder_screen(state, default) + if row.value in CARD_PLATFORMS: + return _open_folder_screen(state, None) + # The folder detected here before the built-in one: the built-in + # default was offered as where RetroArch reads, when the + # retroarch.cfg found here names another. + found = next((path for plat, path, _label in state.setups if plat == row.value), None) + if found is not None: + return _open_folder_screen(state, found, "found") + return _open_folder_screen(state, _default_dest(state.os_type, row.value)) if screen == "folder": if action == "default": return _choose(state, state.platform, state.folder_default) @@ -11165,7 +11182,7 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]: if action == "quit": return tui_quit(state) if action == "folder": - return _open_folder_screen(state, state.dest) + return _open_folder_screen(state, state.dest, "chosen") if action == "fromcard": return _open_from_browser(state) if action in ("misplaced", "unreadable"): @@ -11236,7 +11253,7 @@ def _option(state: TuiState, action: str, step: int) -> list[tuple]: elif action in ("regions", "command"): return _push(state, action) elif action == "folder": - return _open_folder_screen(state, state.dest) + return _open_folder_screen(state, state.dest, "chosen") elif action == "from" and step < 0: # Said, and the screen stays: Back to the plan counts again. if state.from_folders: @@ -12052,7 +12069,13 @@ def _view_folder(state: TuiState, width: int, height: int) -> tuple: "root folder, the one holding .system."] elif state.folder_default is not None: title = "Which folder?" - before = [f"{name} usually reads its BIOS files from:", str(state.folder_default)] + lead = {"found": f"{name} reads its BIOS files from:", + "chosen": f"The BIOS folder chosen for {name}:"}.get( + state.folder_kind, f"{name} usually reads its BIOS files from:") + before = [lead, str(state.folder_default)] + elif state.folder_kind == "closed": + title = "Which folder?" + before = [f"Choose another folder for {name}: browse for it or type it."] else: title = "Which folder?" before = [f"No usual {name} folder here: browse for it or type it."] diff --git a/tests/test_install_tui.py b/tests/test_install_tui.py index aa81f1b87..6dfde1c36 100644 --- a/tests/test_install_tui.py +++ b/tests/test_install_tui.py @@ -3783,5 +3783,65 @@ class EndToEnd(unittest.TestCase): self.assertNotIn(b"\x1b", term.raw) +class TheFolderScreenSaysWhatItOffers(unittest.TestCase): + """The folder screen said "usually reads its BIOS files from" for any + folder it offered: the folder named with --dest, reached by Change + folder, and the built-in default while the retroarch.cfg found here + names another. A closed folder found here opened on "No usual + RetroArch folder here" under the setup that listed it.""" + + def said(self, state: install.TuiState) -> str: + return " ".join(" ".join(install.tui_view(state, 200, 40)).split()) + + def test_change_folder_names_the_chosen_folder(self): + state = checked_state() + state.stack = ["setup", "plan"] + mine = Path("/home/user/typed/mybios") + state.dest = mine + install._activate(state, install.TuiRow("folder", "Change folder")) + self.assertEqual(state.screen, "folder") + screen = self.said(state) + self.assertNotIn("usually reads", screen) + self.assertIn(f"The BIOS folder chosen for RetroArch: {mine}", screen) + + def test_another_setup_offers_the_folder_found_here(self): + state = fresh_state() + found = Path("/home/user/Games/bios") + state.setups = [("retroarch", found, "RetroArch")] + state.stack = ["setup", "platforms"] + install._activate(state, install.TuiRow("platform", "RetroArch", value="retroarch")) + self.assertEqual(state.folder_default, found) + self.assertIn(f"RetroArch reads its BIOS files from: {found}", self.said(state)) + + def test_with_none_found_the_usual_folder_is_offered(self): + state = fresh_state() + state.setups = [] + state.stack = ["setup", "platforms"] + install._activate(state, install.TuiRow("platform", "RetroArch", value="retroarch")) + self.assertIn("RetroArch usually reads its BIOS files from:", self.said(state)) + + def test_a_closed_folder_found_here_asks_for_another(self): + state = fresh_state() + state.stack = ["setup"] + with mock.patch.object(install, "closed_folder", + return_value=(Path("/home/user/lock"), "Permission denied")): + install.tui_update(state, Key("enter")) + self.assertEqual(state.screen, "folder") + screen = self.said(state) + self.assertNotIn("No usual RetroArch folder here", screen) + self.assertIn("Choose another folder for RetroArch", screen) + + def test_a_refused_folder_puts_browse_under_a(self): + for facts in ({"free": 10, "writable": True, "cores": []}, + {"free": 50 * 1024 ** 3, "writable": False, "cores": []}): + with self.subTest(facts=facts): + state = checked_state(facts) + self.assertEqual(install.tui_rows(state)[0].action, "folder") + install.tui_update(state, Key("enter")) + self.assertEqual(state.screen, "folder") + rows = install.tui_rows(state) + self.assertEqual(rows[state.focus["folder"]].action, "browse") + + if __name__ == "__main__": unittest.main()