From 2670b11c2b911e7f2d5e7dc3c3a327a74dd17ebc Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 14:19:18 +0200 Subject: [PATCH] fix: count files shared by systems apart The systems step and --list-systems gave 3do 1704 files, 349 MB, of which 1689 are MAME hash and plugin files six systems carry. Each line now shows the system's own files, and the shared ones beside them. --- install.ps1 | 2 +- install.py | 35 ++++++++++++++++++++++++-------- install.sh | 2 +- tests/test_install_selection.py | 36 +++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 10 deletions(-) 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,