chore: merge old card copy in the interface

This commit is contained in:
Abdessamad Derraz committed 2026-10-23 17:11:15 +02:00
commit 5d8d7ce371
14 files changed
+326 -55

No files matched your search

+1 -1
View File
@@ -8,6 +8,7 @@
Cores: all --core
Parallel downloads: 8 --jobs
BIOS folder: ~/.config/retroarch/system --dest
Copy from: none --from
Preview the file list --dry-run
Show the command line
@@ -34,7 +35,6 @@
A/Enter Open B/Esc Back Left/Right Set ? Help
+1 -1
View File
@@ -6,7 +6,7 @@
Cores: all
Parallel downloads: 8
BIOS folder: ~/.config...ch/system
Copy from: none
Preview the file list
Show the command line
A Open B Back <> Set ? Help
+1 -1
View File
@@ -6,10 +6,10 @@
Cores: all
Parallel downloads: 8
BIOS folder: ~/.config...ch/system
Copy from: none
Preview the file list
Show the command line
Sets --region
A Open B Back <> Set ? Help
+1 -1
View File
@@ -8,6 +8,7 @@
Cores: all --core
Parallel downloads: 8 --jobs
BIOS folder: ~/.config/retroarch/system --dest
Copy from: none --from
Preview the file list --dry-run
Show the command line
@@ -20,5 +21,4 @@
A/Enter Open B/Esc Back Left/Right Set ? Help
+1 -1
View File
@@ -9,6 +9,7 @@
> Install
Choose systems
Copy the BIOS files from your old card or a folder
Options
Quit
@@ -34,7 +35,6 @@
A/Enter Install B/Esc Back Y/o Options ? Help
+1 -1
View File
@@ -4,9 +4,9 @@
1 in place, 3 missing, 1 wrong copy
BIOS folder:
~/.config/retroarch/system
Free space: 50.0 GB
> Install
Choose systems
Copy from your old card or a folder
Options
Quit
A Install B Back Y Options ? Help
+1 -1
View File
@@ -7,9 +7,9 @@
Free space: 50.0 GB
They are replaced, and the old copies
are kept in a backup folder.
> Install
Choose systems
Copy from your old card or a folder
Options
Quit
A Install B Back Y Options ? Help
+1 -1
View File
@@ -9,6 +9,7 @@
> Install
Choose systems
Copy the BIOS files from your old card or a folder
Options
Quit
@@ -20,5 +21,4 @@
A/Enter Install B/Esc Back Y/o Options ? Help
+18 -8
View File
@@ -331,20 +331,30 @@ class EventsForAFolder(FromFolder):
class TheInterfaceReadsAFoundEvent(unittest.TestCase):
"""The full-screen interface drives the engine through --events. A
found event, which an install writes for a misplaced file even without
--from, leaves its screens as they were."""
--from, is what the plan counts after a check and what the download
speed leaves out during an install. Elsewhere it changes nothing."""
FOUND = {"event": "found", "platform": "retroarch", "dest": "scph5501.bin",
"folder": "/bios", "path": "/bios/bios/scph5501.bin", "member": "",
"reason": "nested folder: it is at bios/bios/scph5501.bin"}
def test_every_run_ignores_it(self):
for run in ("check", "confirm", "preview", "install"):
def receive(self, run: str) -> tuple:
state = install.TuiState()
state.run, state.run_id, state.running = run, 3, True
before = copy.deepcopy(vars(state))
self.assertEqual(install.tui_receive(state, ("event", 3, dict(self.FOUND))), [])
return state, before
def test_a_check_counts_it_and_an_install_copies_it(self):
state, _before = self.receive("check")
self.assertEqual(state.found, [self.FOUND])
state, _before = self.receive("install")
self.assertEqual(state.local_dests, {"scph5501.bin"})
def test_a_dry_run_ignores_it(self):
for run in ("confirm", "preview"):
with self.subTest(run=run):
state = install.TuiState()
state.run, state.run_id, state.running = run, 3, True
before = copy.deepcopy(vars(state))
self.assertEqual(install.tui_receive(state, ("event", 3, dict(self.FOUND))),
[])
state, before = self.receive(run)
self.assertEqual(vars(state), before)
+161 -11
View File
@@ -75,6 +75,7 @@ def _args(**given) -> argparse.Namespace:
no_input=False, dry_run=False, yes=False, quiet=False, events=False,
restore=None, from_pack=None, manifest=None, tui=False, plain=False,
)
values["from"] = None
values.update(given)
return argparse.Namespace(**values)
@@ -400,18 +401,22 @@ class Flow(unittest.TestCase):
self.assertEqual(state.exit_code, 0)
def test_every_run_reads_the_folders_given_with_from(self):
"""--tui --from OLD: the check and the install both copy from OLD."""
"""--tui --from OLD: the check and the install both copy from OLD,
each folder named once."""
args = _args(**{"from": [Path("old-card"), Path("backup.zip")],
"from_pack": [Path("pack.zip")]})
flags = install._tui_passthrough(args)
self.assertIn(("--from", str(Path("old-card").absolute())), flags)
self.assertIn(("--from", str(Path("backup.zip").absolute())), flags)
self.assertIn(("--from-pack", str(Path("pack.zip").absolute())), flags)
with mock.patch.object(install, "_mounted_cards", return_value=[]), \
mock.patch.object(install, "detect_platforms", return_value=[]), \
mock.patch.object(install, "detect_frontends", return_value=[]):
started, _effects = install.tui_start(args, "linux")
state = checked_state()
state.passthrough = flags
state.passthrough = started.passthrough
state.from_folders = started.from_folders
for run in ("check", "install"):
self.assertIn(f"--from={Path('old-card').absolute()}",
install.tui_argv(state, run, "py", "i.py"))
argv = install.tui_argv(state, run, "py", "i.py")
for folder in ("old-card", "backup.zip"):
self.assertEqual(argv.count(f"--from={Path(folder).absolute()}"), 1, argv)
self.assertIn(f"--from-pack={Path('pack.zip').absolute()}", argv)
def test_the_check_runs_with_the_command_line_choices(self):
state = fresh_state()
@@ -468,7 +473,7 @@ class Flow(unittest.TestCase):
rows = install.tui_rows(state)
self.assertEqual([row.right for row in rows], [
"--region", "--no-game-data", "--installed-cores", "--core", "--jobs", "--dest",
"--dry-run", ""])
"--from", "--dry-run", ""])
press(state, "enter")
self.assertEqual(state.screen, "regions")
# Ticked in the order of preference, then moved up.
@@ -512,7 +517,7 @@ class Flow(unittest.TestCase):
def test_preview_is_a_dry_run(self):
state = checked_state()
press(state, "o")
state.focus["options"] = 6
state.focus["options"] = 7
self.assertEqual(press(state, "enter"), [("run", "preview")])
self.assertEqual(install.tui_argv(state, "preview", "py", "i.py")[-2:],
["--dry-run", "--events"])
@@ -683,7 +688,7 @@ class Flow(unittest.TestCase):
def test_events_of_an_abandoned_run_are_ignored(self):
state = checked_state()
press(state, "o")
state.focus["options"] = 6
state.focus["options"] = 7
press(state, "enter")
old = state.run_id
self.assertEqual(press(state, "esc"), [("kill",)])
@@ -1590,6 +1595,121 @@ class WorkFolders(unittest.TestCase):
self.assertFalse(work.exists())
def found(dest: str, folder: str, reason: str = "") -> dict:
return {"event": "found", "platform": "retroarch", "dest": dest, "folder": folder,
"path": f"{folder}/{dest}", "member": "", "reason": reason}
class CopyFromAnOldCard(unittest.TestCase):
"""--from in the interface: a row on the plan, the browser, the counts."""
def recheck(self, state: install.TuiState, events: list) -> None:
for event in [*FILES, *SYSTEMS, *events, CHECK]:
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, []))
def test_the_plan_offers_it_and_the_browser_adds_the_folder(self):
old = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, old, True)
(old / "bios").mkdir()
(old / "backup.zip").write_bytes(b"PK")
state = checked_state()
row = next(i for i, r in enumerate(install.tui_rows(state)) if r.action == "fromcard")
self.assertEqual(install.tui_rows(state)[row].label,
"Copy the BIOS files from your old card or a folder")
state.focus["plan"] = row
state.home = old
with mock.patch.object(install, "_mounted_cards", return_value=[]):
press(state, "enter")
self.assertEqual((state.screen, state.browse_for), ("browse", "from"))
labels = [r.label for r in install.tui_rows(state)]
self.assertEqual(labels, ["Use this folder", "..", "bios/", "backup.zip"])
self.assertIn("Choose the old card", "\n".join(install.tui_view(state, 80, 24)))
effects = press(state, "enter")
self.assertEqual(effects, [("run", "check")])
self.assertEqual(state.from_folders, [old])
self.assertIn(f"--from={old}", install.tui_argv(state, "check", "py", "i.py"))
self.recheck(state, [found("scph5500.bin", str(old)), found("gba_bios.bin", str(old))])
self.assertEqual(state.screen, "plan")
text = "".join("".join(install.tui_view(state, 120, 40)).split())
self.assertIn("".join(f"2 found in {old}, 2 to download (512.0 KB)".split()), text)
# A second folder from the options: --from twice.
press(state, "o")
state.focus["options"] = next(i for i, r in enumerate(install.tui_rows(state))
if r.action == "from")
self.assertEqual(install.tui_rows(state)[state.focus["options"]].right, "--from")
with mock.patch.object(install, "_mounted_cards", return_value=[]):
press(state, "enter")
# It opens beside the first folder: the second card sits next to it.
self.assertEqual(state.browse_dir, old.parent)
install._browse_to(state, old)
state.focus["browse"] = 3
self.assertEqual(press(state, "enter"), [("run", "check")])
self.assertEqual(state.from_folders, [old, old / "backup.zip"])
self.assertEqual([flag for flag in install.tui_argv(state, "check", "py", "i.py")
if flag.startswith("--from=")],
[f"--from={old}", f"--from={old / 'backup.zip'}"])
def test_left_takes_the_last_folder_back(self):
state = checked_state()
state.from_folders = [Path("/media/old")]
state.stack.append("options")
state.focus["options"] = next(i for i, r in enumerate(install.tui_rows(state))
if r.action == "from")
self.assertEqual(press(state, "left"), [("run", "check")])
self.assertEqual(state.from_folders, [])
def test_the_command_line_folders_are_kept(self):
with mock.patch.object(install, "detect_platforms", return_value=[]), \
mock.patch.object(install, "detect_frontends", return_value=[]), \
mock.patch.object(install, "_mounted_cards", return_value=[]):
args = _args(platform="retroarch", dest=SYSTEM_DIR)
setattr(args, "from", [Path("/media/old")])
state, _effects = install.tui_start(args, "linux")
self.assertEqual(state.from_folders, [Path("/media/old")])
self.assertIn(("--from", "/media/old"), install.tui_flags(state))
def test_files_in_the_wrong_place_are_said_and_listed(self):
state = checked_state()
state.platform, state.dest = "retroarch", SYSTEM_DIR
install._start_check(state)
self.recheck(state, [found("scph5500.bin", str(SYSTEM_DIR),
"nested folder: bios/scph5500.bin")])
text = " ".join(line.strip() for line in install.tui_view(state, 120, 40))
self.assertIn("1 file is in the wrong place, it will be copied where the emulators "
"read it.", text)
row = next(i for i, r in enumerate(install.tui_rows(state)) if r.action == "misplaced")
state.focus["plan"] = row
press(state, "enter")
self.assertEqual(state.screen, "misplaced")
self.assertIn("scph5500.bin: nested folder: bios/scph5500.bin",
"\n".join(install.tui_view(state, 80, 24)))
press(state, "esc")
self.assertEqual(state.screen, "plan")
def test_a_local_copy_is_no_download_speed(self):
state = checked_state()
press(state, "enter")
mega = 1024 * 1024
events = [(9.0, {"event": "found", **found("big.bin", "/media/old")}),
(9.0, {"event": "download", "platform": "retroarch", "files": 2,
"bytes": 300 * mega}),
(10.0, {"event": "downloaded", "dest": "big.bin", "ok": True, "reason": "",
"size": 200 * mega, "files_done": 1, "files_total": 2,
"bytes_done": 200 * mega, "bytes_total": 300 * mega}),
(11.0, {"event": "progress", "files_done": 1, "files_total": 2,
"bytes_done": 201 * mega, "bytes_total": 300 * mega}),
(12.0, {"event": "progress", "files_done": 1, "files_total": 2,
"bytes_done": 202 * mega, "bytes_total": 300 * mega})]
for when, event in events:
state.now = when
install.tui_receive(state, ("event", state.run_id, event))
text = "\n".join(install.tui_view(state, 80, 24))
self.assertIn("1.0 MB/s, 1:38 left", text)
class Crash(unittest.TestCase):
"""An unexpected error ends on one sentence and a log, never a traceback."""
@@ -1890,6 +2010,36 @@ class EndToEnd(unittest.TestCase):
time.sleep(1)
self.assertEqual(len(repo.installed()), on_disk)
def test_copy_from_an_old_card(self):
repo = self.repo()
old = Path(self.env["HOME"]) / "oldcard"
(old / "bios").mkdir(parents=True)
for name in ("scph5500.bin", "scph5501.bin", "gba_bios.bin"):
payload = FakeRepository.PAYLOADS[name][0]
(old / "bios" / name).write_bytes(payload)
term = self.start("--platform", "retroarch", "--dest", str(repo.dest))
term.wait("5 files to download")
term.send(DOWN, DOWN, "\r")
term.wait("Choose the old card")
for _ in range(12):
if "> oldcard/" in term.text():
break
term.send(DOWN)
term.send("\r")
term.wait("> Use this folder")
term.send("\r")
screen = term.wait("found in")
# The folder wraps at a slash: compared without the spaces.
self.assertIn("".join(f"3 found in {old}, 2 to download (8 B)".split()),
"".join(screen.split()))
# Checked again, the plan opens on Install.
term.send("\r")
term.wait("files installed.")
term.send("\r")
self.assertEqual(term.close(), 0)
self.assertEqual(len(repo.installed()), 5)
self.assertIn(f"--from {old}", term.after().replace("\n", " "))
def test_ctrl_c_on_the_first_screen(self):
self.repo()
term = self.start()