diff --git a/install.ps1 b/install.ps1 index cfb47f8b..365f4af6 100644 --- a/install.ps1 +++ b/install.ps1 @@ -11,7 +11,7 @@ $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" - $defaultInstallSha256 = "739a28ac23d5b0d82d8324ecd7665b2501c71c64d60abba175575f7f16f24ddc" + $defaultInstallSha256 = "441fc1f7887aff7808b7b41182fdb18868ce36a50a2bbbba9bb81e595cb3b732" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index 383f73db..6401e56e 100644 --- a/install.py +++ b/install.py @@ -5742,12 +5742,32 @@ def _sized(files: list[dict]) -> str: NO_CORE_FILES = "no extra file: it reads the platform's own, always kept" +def _core_sized(files: list[dict]) -> str: + return _sized(files) if files else NO_CORE_FILES + + +def _system_sized(files: list[dict]) -> str: + """A system's files, those other systems share counted apart. + + `3do 1704 files, 349.0 MB` read as the 3DO BIOS: 1689 of them are the + MAME hash and plugin files six systems carry. + """ + shared = [f for f in files if len(entry_systems(f)) > 1] + if not shared: + return _sized(files) + if len(shared) == len(files): + return f"{_sized(shared)}, all shared with other systems" + own = [f for f in files if len(entry_systems(f)) <= 1] + return f"{_sized(own)}, and {_sized(shared)} shared with other systems" + + def _pick( - title: str, rows: list[tuple[str, list[dict]]], ordered: bool = False, empty: str = "", + title: str, rows: list[tuple[str, list[dict]]], ordered: bool = False, + sizer: Callable[[list[dict]], str] = _sized, ) -> list[str]: """Numbered list of names. An empty answer keeps every one. - empty is said instead of "0 files, 0 B" for a name that holds none. + sizer says what each name holds. """ if not rows: return [] @@ -5755,8 +5775,7 @@ def _pick( width = len(str(len(rows))) name_width = min(max(len(name) for name, _files in rows), 32) for index, (name, files) in enumerate(rows, 1): - print(f" {index:>{width}}) {name:<{name_width}} " - f"{_sized(files) if files or not empty else empty}") + print(f" {index:>{width}}) {name:<{name_width}} {sizer(files)}") hint = "order of preference, e.g. 2,1" if ordered else "e.g. 1,3-5" while True: text = _ask(f"Numbers ({hint}), Enter for all, q to quit: ") @@ -5814,7 +5833,7 @@ def _prompt_custom_selection( systems = _pick("Systems:", [ (name, [f for f in files if _chosen(f, {name})]) for name in choices["systems"] - ]) + ], sizer=_system_sized) kept, kept_omitted = narrow(files, omitted, systems, [], []) cores: list[str] = [] @@ -5826,7 +5845,7 @@ def _prompt_custom_selection( (name, _filter_by_target( [f for f in kept if f.get("cores") is not None], [name])) for name in core_names - ], empty=NO_CORE_FILES) + ], sizer=_core_sized) kept, kept_omitted = narrow(kept, kept_omitted, [], cores, []) regions: list[str] = [] @@ -5887,8 +5906,8 @@ def _print_choices( if systems else _filter_by_target([f for f in files if f.get("cores") is not None], [name]) ) - shown = NO_CORE_FILES if not systems and not held else _sized(held) - print(f" {labels[name]:<{width}} {shown}") + print(f" {labels[name]:<{width}} " + f"{_system_sized(held) if systems else _core_sized(held)}") def emulator_names(manifest: dict, files: list[dict], omitted: list[dict]) -> dict[str, str]: diff --git a/install.sh b/install.sh index aefa54ef..bc701f70 100755 --- a/install.sh +++ b/install.sh @@ -18,7 +18,7 @@ esac TEMP_INSTALLER="" TEMP_DIRECTORY="" DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" -DEFAULT_INSTALL_SHA256="739a28ac23d5b0d82d8324ecd7665b2501c71c64d60abba175575f7f16f24ddc" +DEFAULT_INSTALL_SHA256="441fc1f7887aff7808b7b41182fdb18868ce36a50a2bbbba9bb81e595cb3b732" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_install_selection.py b/tests/test_install_selection.py index e4764790..667a8d21 100644 --- a/tests/test_install_selection.py +++ b/tests/test_install_selection.py @@ -426,6 +426,42 @@ class ACoreWithNoFileOfItsOwn(unittest.TestCase): self.assertIn("always kept", line) +class FilesSharedBySystemsAreCountedApart(unittest.TestCase): + """`3do 1704 files, 349.0 MB`: 1689 of them are MAME's hash and plugin + files, which six systems carry. Picking 3DO, one expects its BIOS.""" + + FILES = [ + {"dest": "panafz10.bin", "size": 4, "cores": None, "systems": ["3do"]}, + {"dest": "mame/hash/a.xml", "size": 100, "cores": None, + "systems": ["3do", "philips-cdi"]}, + {"dest": "mame/hash/b.xml", "size": 50, "cores": None, + "systems": ["3do", "philips-cdi"]}, + {"dest": "gba_bios.bin", "size": 8, "cores": None, "systems": ["nintendo-gba"]}, + ] + + def _listing(self) -> str: + out = io.StringIO() + with contextlib.redirect_stdout(out): + install._print_choices("retroarch", self.FILES, [], systems=True) + return out.getvalue() + + def test_the_listing(self): + lines = {line.split()[0]: line for line in self._listing().splitlines()} + self.assertIn("1 file, 4 B, and 2 files, 150 B shared with other systems", + lines["3do"]) + self.assertIn("2 files, 150 B, all shared with other systems", lines["philips-cdi"]) + self.assertTrue(lines["nintendo-gba"].endswith("1 file, 8 B")) + + def test_the_systems_step(self): + answers = iter(["c", ""]) + out = io.StringIO() + with mock.patch("builtins.input", lambda _prompt="": next(answers)), \ + contextlib.redirect_stdout(out): + install._prompt_custom_selection("retroarch", self.FILES, []) + self.assertIn("1 file, 4 B, and 2 files, 150 B shared with other systems", + out.getvalue()) + + class TheBoundaryChecksTheNewFields(unittest.TestCase): def _manifest(self, **extra): entry = {"dest": "a.bin", "sha1": "a" * 40, "size": 1,