mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 07:33:45 -06:00
fix: ask a card setup for its own card
A card system chosen on a computer, from --platform, the setup list, its filter or the line-mode menu, always asks for its card. The card screen lists only the cards whose files say they run that system, offers Look again, and checks a browsed, typed or --dest folder as --dest is checked. The line-mode Card root prompt lists the mounted drives, asks again on a missing or foreign root, and asks before creating several folders. On the device the detected folder is offered, and the bound card is forgotten when another setup is chosen.
This commit is contained in:
1 parent
44378ae5c9
commit
44f94ab703
15 files changed
+629
-153
No files matched your search
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
$defaultInstallSha256 = "000b7e9e965f6d81c254d7ee7967077f9bbae8aca2286179f31d4f4f7b555d06"
|
||||
$defaultInstallSha256 = "8833256124ffdfa38865e7b065f2720ec20997e1a91ba340eab4067e31b20727"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installerExit = 0
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
|
||||
+273
-71
@@ -2617,11 +2617,12 @@ def did_you_mean(
|
||||
return f"{listing} lists all {len(known)}."
|
||||
|
||||
|
||||
def normalize_platform(name: str) -> str:
|
||||
def normalize_platform(name: str, say: bool = True) -> str:
|
||||
"""Return the canonical platform id for user-supplied input.
|
||||
|
||||
The site writes MiSTer FPGA and RetroArch: letters and digits decide,
|
||||
as they do for verify.py and generate_pack.py.
|
||||
as they do for verify.py and generate_pack.py. say is False where the
|
||||
name is read again later, which says it once.
|
||||
"""
|
||||
key = _name_key(name)
|
||||
for plat in AVAILABLE_PLATFORMS:
|
||||
@@ -2631,8 +2632,9 @@ def normalize_platform(name: str) -> str:
|
||||
# refused: a system named there stands for the list it gets.
|
||||
for rule in _EMBEDDED_RULES:
|
||||
if key and rule.manifest and key == _name_key(rule.name):
|
||||
print(f" {rule.name} gets the {display_name(rule.manifest)} file list: "
|
||||
f"--platform {rule.manifest}.")
|
||||
if say:
|
||||
print(f" {rule.name} gets the {display_name(rule.manifest)} file list: "
|
||||
f"--platform {rule.manifest}.")
|
||||
return rule.manifest
|
||||
print(f"Unknown platform '{name.strip()}'. "
|
||||
+ did_you_mean([name], list(AVAILABLE_PLATFORMS), "--list-platforms"),
|
||||
@@ -6275,10 +6277,16 @@ def _choose_platform(title: str, default: str = "") -> str:
|
||||
"or q to quit.")
|
||||
|
||||
|
||||
def _prompt_manual_platform(os_type: str) -> list[tuple[str, Path]]:
|
||||
"""Ask for a platform and a BIOS directory when nothing was detected."""
|
||||
def _prompt_manual_platform(os_type: str, ask: bool = True) -> list[tuple[str, Path]]:
|
||||
"""Ask for a platform and a BIOS directory when nothing was detected.
|
||||
|
||||
A system that runs from its card gets the card's root asked for, as
|
||||
--platform rocknix does: /storage/roms/bios was offered on a PC.
|
||||
"""
|
||||
plat = _choose_platform("No supported platform found here. Choose the one to "
|
||||
"install for:", "retroarch")
|
||||
if card_bound(plat, plat) is not None:
|
||||
return [(plat, card_install_dest(plat, plat, True, ask))]
|
||||
default_dest = _default_dest(os_type, plat)
|
||||
raw = ""
|
||||
while not raw:
|
||||
@@ -9257,7 +9265,7 @@ def _open_local_sources(args: argparse.Namespace) -> None:
|
||||
+ (f", {parts} parts" if parts > 1 or _PART_FILE.match(pack.files[0].name) else ""))
|
||||
spelled = pack.platforms()
|
||||
if args.platform is not None:
|
||||
plat = normalize_platform(args.platform)
|
||||
plat = normalize_platform(args.platform, say=False)
|
||||
if spelled and plat not in spelled:
|
||||
print(f"Error: {pack.name} is a pack for "
|
||||
f"{' and '.join(display_name(n) for n in spelled)}, not "
|
||||
@@ -9495,18 +9503,31 @@ def _ends_with(path: Path, relative: str) -> bool:
|
||||
return len(parts) >= len(tail) and parts[len(parts) - len(tail):] == tail
|
||||
|
||||
|
||||
def card_dest(named: str, plat: str, dest: Path) -> tuple[str, Path]:
|
||||
"""The file list and folder for a --dest that names a handheld's card.
|
||||
class CardChoice(NamedTuple):
|
||||
"""Where the files of a folder named for a card go, and what is said: a
|
||||
note, or why the folder is not the card of the system named."""
|
||||
|
||||
plat: str
|
||||
folder: Path
|
||||
note: str = ""
|
||||
refused: str = ""
|
||||
# The system's folder below its card, and the file that names the card.
|
||||
below: str = ""
|
||||
marker: str = ""
|
||||
|
||||
|
||||
def card_choice(named: str, plat: str, dest: Path) -> CardChoice:
|
||||
"""The file list and folder for a folder that may be a handheld's card.
|
||||
|
||||
The card's root is where the user points, its BIOS folder is below it:
|
||||
`--platform muos --dest /media/me/SD1` wrote to the root, which muOS
|
||||
never reads. A system named on the command line whose card this is not
|
||||
is refused with the folder it reads.
|
||||
never reads. A system named whose card this is not is refused with the
|
||||
folder it reads.
|
||||
"""
|
||||
found = card_system(dest)
|
||||
if found and (not plat or found[0] == plat) and found[1] != dest:
|
||||
print(f" {found[2]} card: {display_name(found[0])} files, in {found[1]}")
|
||||
return found[0], found[1]
|
||||
return CardChoice(found[0], found[1],
|
||||
f"{found[2]} card: {display_name(found[0])} files, in {found[1]}")
|
||||
# Only a system named by its own name: --platform retroarch or knulli
|
||||
# says which list, not which card.
|
||||
key = _name_key(named)
|
||||
@@ -9516,23 +9537,48 @@ def card_dest(named: str, plat: str, dest: Path) -> tuple[str, Path]:
|
||||
if rule is None or found:
|
||||
if len({(match.manifest, match.bios) for match in matched}) > 1:
|
||||
names = " and ".join(sorted({match.name for match in matched}))
|
||||
print(f" {screen_text(str(dest))} holds the files of {names}: installing into "
|
||||
"it as named. Name the card's BIOS folder with --dest to write there.")
|
||||
return plat, dest
|
||||
return CardChoice(plat, dest, f"{screen_text(str(dest))} holds the files of "
|
||||
f"{names}: installing into it as named. Name the card's BIOS "
|
||||
"folder with --dest to write there.")
|
||||
return CardChoice(plat, dest)
|
||||
# Its folder on the card, or one its device reads (/run/muos/storage/bios).
|
||||
own = {device.bios for device in _EMBEDDED_RULES if device.name == rule.name}
|
||||
if dest.as_posix() in own or _ends_with(dest, rule.bios):
|
||||
return plat, dest
|
||||
return CardChoice(plat, dest)
|
||||
if any(match.name == rule.name for match in matched):
|
||||
# A card two systems claim, the one named among them.
|
||||
print(f" {rule.name} card: {display_name(plat)} files, in {dest / rule.bios}")
|
||||
return plat, dest / rule.bios
|
||||
return CardChoice(plat, dest / rule.bios, f"{rule.name} card: {display_name(plat)} "
|
||||
f"files, in {dest / rule.bios}")
|
||||
marker = rule.values[0].split("/")[0]
|
||||
print(f"Error: {dest} is not a {rule.name} card: it holds no {marker}. "
|
||||
f"{rule.name} reads its BIOS files from {rule.bios} on its card: name the card's "
|
||||
f"root (the folder holding {marker}) or that folder: --dest <card>/{rule.bios}",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return CardChoice(plat, dest, refused=f"{dest} is not a {rule.name} card: it holds no "
|
||||
f"{marker}. {rule.name} reads its BIOS files from {rule.bios} on its "
|
||||
"card", below=rule.bios, marker=marker)
|
||||
|
||||
|
||||
def card_dest(named: str, plat: str, dest: Path) -> tuple[str, Path]:
|
||||
"""The file list and folder for a --dest that names a handheld's card,
|
||||
the run ended on a folder that is not the card of the system named."""
|
||||
choice = card_choice(named, plat, dest)
|
||||
if choice.refused:
|
||||
print(f"Error: {choice.refused}: name the card's root (the folder holding "
|
||||
f"{choice.marker}) or that folder: --dest <card>/{choice.below}",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if choice.note:
|
||||
print(f" {choice.note}")
|
||||
return choice.plat, choice.folder
|
||||
|
||||
|
||||
def card_root_above(dest: Path) -> tuple[str, Path] | None:
|
||||
"""(platform, root) of the MinUI-family card dest is inside, when dest is
|
||||
a folder below its root and not the root."""
|
||||
if card_platform(dest) is not None:
|
||||
return None
|
||||
for parent in list(dest.absolute().parents)[:2]:
|
||||
holder = card_platform(parent)
|
||||
if holder:
|
||||
return holder, parent
|
||||
return None
|
||||
|
||||
|
||||
def _check_card_root(plat: str, dest: Path) -> None:
|
||||
@@ -9541,16 +9587,13 @@ def _check_card_root(plat: str, dest: Path) -> None:
|
||||
The file list writes Bios/<TAG>/ below --dest: given the card's Bios
|
||||
folder, every file would land in Bios/Bios/<TAG>/, which no core reads.
|
||||
"""
|
||||
above = card_root_above(dest)
|
||||
if above:
|
||||
print(f"Error: {dest} is inside a {display_name(above[0])} card. "
|
||||
f"Name the card's root: --dest {above[1]}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
found = card_platform(dest)
|
||||
if found is None:
|
||||
for parent in list(dest.absolute().parents)[:2]:
|
||||
holder = card_platform(parent)
|
||||
if holder:
|
||||
print(f"Error: {dest} is inside a {display_name(holder)} card. "
|
||||
f"Name the card's root: --dest {parent}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return
|
||||
if found != plat:
|
||||
if found is not None and found != plat:
|
||||
print(f" Note: this card holds {display_name(found)} ({CARD_MARKER}), and the "
|
||||
f"{display_name(plat)} file list was asked for.")
|
||||
|
||||
@@ -9579,13 +9622,50 @@ def mounted_cards_of(system: str, plat: str) -> list[Path]:
|
||||
if any(rule.name == system for rule in _card_matches(mount))]
|
||||
|
||||
|
||||
def card_install_dest(named: str, plat: str, interactive: bool) -> Path:
|
||||
def card_root_folder(named: str, plat: str, root: Path) -> CardChoice:
|
||||
"""The BIOS folder for a root chosen as the card of a card-bound system,
|
||||
or why root is not that card.
|
||||
|
||||
A typed root was taken as it was: a typo four folders deep was created
|
||||
and filled, a menu answer (8) became a relative card, and a folder that
|
||||
was no Onion card ended the run. Each is refused here, with its reason,
|
||||
for the question to be asked again.
|
||||
"""
|
||||
system, below = card_bound(named, plat) or (display_name(plat), "")
|
||||
# The card is mounted, its BIOS folder may not be made yet.
|
||||
card = (root.parents[len(PurePosixPath(below).parts) - 1]
|
||||
if below and _ends_with(root, below) else root)
|
||||
if not _is_dir(card):
|
||||
return CardChoice(plat, root, refused=f"{card} does not exist, or is not a folder")
|
||||
if plat in CARD_PLATFORMS:
|
||||
above = card_root_above(root)
|
||||
if above:
|
||||
return CardChoice(plat, root, refused=f"{root} is inside a "
|
||||
f"{display_name(above[0])} card, whose root is {above[1]}")
|
||||
found = card_platform(root)
|
||||
if found is None:
|
||||
return CardChoice(plat, root, refused=f"{root} holds no {CARD_MARKER}: it is "
|
||||
f"not a {system} card")
|
||||
note = ("" if found == plat else f"Note: this card holds {display_name(found)} "
|
||||
f"({CARD_MARKER}), and the {display_name(plat)} file list was asked for.")
|
||||
return CardChoice(plat, root, note)
|
||||
choice = card_choice(named, plat, root)
|
||||
if choice.refused or choice.folder != root or not below or _ends_with(root, below):
|
||||
return choice
|
||||
# A KNULLI share or a ROCKNIX card read as a list's name: its folder
|
||||
# below the root, as the device reads it.
|
||||
return choice._replace(folder=root / below)
|
||||
|
||||
|
||||
def card_install_dest(named: str, plat: str, interactive: bool, ask: bool = True) -> Path:
|
||||
"""The folder for a card-bound system named without --dest, on a machine
|
||||
that is not the device.
|
||||
|
||||
One mounted card that runs it is taken. At a terminal the card's root is
|
||||
asked for, never this machine's own folder. Without questions the run
|
||||
stops on the --dest that names the card.
|
||||
asked for, never this machine's own folder, and asked again until it is
|
||||
that card. Without questions the run stops on the --dest that names the
|
||||
card. ask is whether creating several missing folders is asked first,
|
||||
as it is for --dest.
|
||||
"""
|
||||
system, below = card_bound(named, plat) or (display_name(plat), "")
|
||||
cards = mounted_cards_of(system, plat)
|
||||
@@ -9606,21 +9686,27 @@ def card_install_dest(named: str, plat: str, interactive: bool) -> Path:
|
||||
f"Mount the card and name its root with --dest, for example: {example}",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
# The drives the interface lists, each with what its own files say.
|
||||
mounts = _mounted_cards()
|
||||
print(f" {system} keeps its BIOS files on its SD card, not on this machine. "
|
||||
"Mount the card, then type its root folder.")
|
||||
raw = ""
|
||||
while not raw:
|
||||
"Mount the card, then type its root folder"
|
||||
+ (" or its number:" if mounts else "."))
|
||||
for index, mount in enumerate(mounts, 1):
|
||||
print(f" {index}) {mount} ({_card_what(mount)})")
|
||||
while True:
|
||||
raw = _ask("Card root (Ctrl+C to stop): ")
|
||||
root = _typed_folder(raw)
|
||||
if plat in CARD_PLATFORMS:
|
||||
_check_card_root(plat, root)
|
||||
return root
|
||||
folder = card_dest(named, plat, root)[1]
|
||||
if folder == root and below and not _ends_with(root, below):
|
||||
# A KNULLI share or a ROCKNIX card read as a list's name: its
|
||||
# folder below the root, as the device reads it.
|
||||
return root / below
|
||||
return folder
|
||||
if not raw:
|
||||
continue
|
||||
root = (mounts[int(raw) - 1] if raw.isdigit() and 1 <= int(raw) <= len(mounts)
|
||||
else _typed_folder(raw))
|
||||
choice = card_root_folder(named, plat, root)
|
||||
if choice.refused:
|
||||
print(f" {choice.refused}. Type the root of the {system} card.")
|
||||
continue
|
||||
if choice.note:
|
||||
print(f" {choice.note}")
|
||||
_missing_dest(choice.folder, ask, "typed")
|
||||
return choice.folder
|
||||
|
||||
|
||||
def _platform_for_dest(os_type: str, dest: Path, interactive: bool) -> str:
|
||||
@@ -9759,8 +9845,9 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
elif bound is not None:
|
||||
# Its files go on its SD card, which is never this machine's
|
||||
# own folder.
|
||||
platforms = [(args.platform, card_install_dest(named, args.platform,
|
||||
interactive))]
|
||||
platforms = [(args.platform, card_install_dest(
|
||||
named, args.platform, interactive,
|
||||
not (args.check or args.dry_run or args.yes)))]
|
||||
elif args.platform in PORTABLE_ONLY:
|
||||
# A portable emulator reads the folder beside its executable:
|
||||
# any default would be a folder it never opens.
|
||||
@@ -9828,9 +9915,11 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
for line in _manual_usage_hint(os_type, asked):
|
||||
print(line)
|
||||
sys.exit(1)
|
||||
platforms = _prompt_manual_platform(os_type)
|
||||
platforms = _prompt_manual_platform(
|
||||
os_type, not (args.check or args.dry_run or args.yes))
|
||||
plat, typed = platforms[0]
|
||||
if typed != _default_dest(os_type, plat):
|
||||
# A card's root was asked for, and its folders checked, there.
|
||||
if typed != _default_dest(os_type, plat) and card_bound(plat, plat) is None:
|
||||
_missing_dest(typed, not (args.check or args.dry_run or args.yes),
|
||||
"typed")
|
||||
else:
|
||||
@@ -10814,6 +10903,8 @@ class TuiState:
|
||||
# (system, BIOS folder below the card root) when the setup asked for
|
||||
# runs from an SD card: the folder screen offers the mounted cards.
|
||||
self.card_bound: tuple[str, str] | None = None
|
||||
# The mounted cards of that system, as last looked for.
|
||||
self.cards: list | None = None
|
||||
# (width and text, lines) of the last pager wrapped.
|
||||
self.pager_cache: tuple | None = None
|
||||
self.path_text: str = ""
|
||||
@@ -11220,18 +11311,41 @@ def tui_rows(state: TuiState) -> list[TuiRow]:
|
||||
rows = [_filter_row(state)]
|
||||
for plat in AVAILABLE_PLATFORMS:
|
||||
named = _setup_matches(plat, systems.get(plat, []), needle)
|
||||
if named is not None:
|
||||
if named is None:
|
||||
continue
|
||||
# A system that runs from its SD card, named or run by a device
|
||||
# typed, is a row of its own that asks for the card: "onion" on
|
||||
# RetroArch's row led to this computer's RetroArch folder.
|
||||
cards = [name for name in named
|
||||
if name in systems.get(plat, []) and card_bound(name, plat)]
|
||||
others = [name for name in named if name not in cards]
|
||||
if not named or others:
|
||||
# The systems the filter named, those a device typed runs:
|
||||
# "miyoo" on RetroArch read "muOS, JELOS, KOLOS...".
|
||||
rows.append(TuiRow("platform", display_name(plat),
|
||||
", ".join(named or systems.get(plat, [])), plat))
|
||||
", ".join(others or systems.get(plat, [])), plat))
|
||||
for name in cards:
|
||||
devices = [device for device in _SETUP_DEVICES.get(name, ())
|
||||
if _matches(device, _device_needle(needle))]
|
||||
rows.append(TuiRow("cardsystem", f"{name}, SD card", ", ".join(devices)
|
||||
or f"{display_name(plat)} files", (plat, name)))
|
||||
return rows
|
||||
if screen == "folder":
|
||||
rows = [TuiRow("default", "Use this folder")] if state.folder_default else []
|
||||
if _tui_card_bound(state):
|
||||
rows += [TuiRow("card", f"{_mount_name(mount)} ({_card_what(mount)})", value=mount)
|
||||
for mount in _mounted_cards()]
|
||||
return rows + [TuiRow("browse", "Browse for a folder"), TuiRow("type", "Type a path")]
|
||||
bound = _tui_card_bound(state)
|
||||
if not bound:
|
||||
return rows + [TuiRow("browse", "Browse for a folder"),
|
||||
TuiRow("type", "Type a path")]
|
||||
# Only the cards whose own files say they run the system: A on the
|
||||
# first mount wrote Onion's layout on a muOS card.
|
||||
mounts = (state.cards if state.cards is not None
|
||||
else mounted_cards_of(bound[0], state.platform))
|
||||
cards = [TuiRow("card", f"{_mount_name(mount)} ({_card_what(mount)})", value=mount)
|
||||
for mount in mounts]
|
||||
rows += cards + [TuiRow("browse", "Browse for a folder"), TuiRow("type", "Type a path")]
|
||||
# On the device itself its folder is found, and no card is looked for.
|
||||
here = state.folder_default is not None and state.folder_kind == "found"
|
||||
return rows + ([] if cards or here else [TuiRow("rescan", "Look again")])
|
||||
if screen == "browse":
|
||||
return list(state.browse_rows)
|
||||
if screen == "create":
|
||||
@@ -11566,9 +11680,54 @@ def _choose(state: TuiState, plat: str, dest: Path) -> list[tuple]:
|
||||
return _start_check(state)
|
||||
|
||||
|
||||
def _card_folder(state: TuiState, folder: Path) -> CardChoice | None:
|
||||
"""What a folder chosen for a setup that runs from its SD card resolves
|
||||
to, as a card root typed in line mode does: None when no card is asked
|
||||
for, or when the folder is inside a MinUI card, whose root _choose
|
||||
offers."""
|
||||
bound = _tui_card_bound(state)
|
||||
if bound is None or (state.platform in CARD_PLATFORMS and card_root_above(folder)):
|
||||
return None
|
||||
return card_root_folder(bound[0], state.platform, folder)
|
||||
|
||||
|
||||
def _card_refusal(state: TuiState, folder: Path) -> str:
|
||||
"""Why folder is not the card the chosen setup runs from, or ""."""
|
||||
choice = _card_folder(state, folder)
|
||||
if choice is None or not choice.refused:
|
||||
return ""
|
||||
return f"{choice.refused}. Choose the root of the {_tui_card_bound(state)[0]} card."
|
||||
|
||||
|
||||
def _choose_card(state: TuiState, folder: Path) -> list[tuple]:
|
||||
"""_choose for a folder picked on the folder, browse or path screen, or
|
||||
given with --dest: for a setup that runs from its SD card, the card's
|
||||
own BIOS folder, and a folder that is not its card refused in words."""
|
||||
choice = _card_folder(state, folder)
|
||||
if choice is None:
|
||||
return _choose(state, state.platform, folder)
|
||||
if choice.refused:
|
||||
state.status = _card_refusal(state, folder)
|
||||
if state.stack and state.screen in ("folder", "browse", "path", "platforms"):
|
||||
return []
|
||||
state.folder_default, state.folder_kind = None, "usual"
|
||||
return _push(state, "folder")
|
||||
card = card_system(folder)
|
||||
if card and card[1] == choice.folder:
|
||||
# Titled as the same card found by itself: "Onion (RetroArch files)".
|
||||
_DETECTED_OS.setdefault((choice.plat, choice.folder), card[2])
|
||||
return _choose(state, choice.plat, choice.folder)
|
||||
|
||||
|
||||
def _open_folder_screen(state: TuiState, default: Path | None,
|
||||
kind: str = "usual") -> list[tuple]:
|
||||
state.folder_default, state.folder_kind = default, kind
|
||||
# The cards are looked for when the screen opens and on Look again: a
|
||||
# card inserted meanwhile moved the rows under the focus.
|
||||
state.cards = None
|
||||
bound = _tui_card_bound(state)
|
||||
if bound:
|
||||
state.cards = mounted_cards_of(bound[0], state.platform)
|
||||
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
|
||||
@@ -11577,7 +11736,11 @@ def _open_folder_screen(state: TuiState, default: Path | None,
|
||||
|
||||
|
||||
def _mount_name(mount: Path) -> str:
|
||||
"""How a person names a mounted card: E:\\ on Windows, its label elsewhere."""
|
||||
"""How a person names a mounted card: E:\\ on Windows, its label elsewhere.
|
||||
Under WSL /mnt/d is the Windows D: drive, which "d" did not say."""
|
||||
if (_WSL["active"] and mount.parent == Path(_WSL["root"]) and len(mount.name) == 1
|
||||
and mount.name.isalpha()):
|
||||
return f"{mount.name.upper()}:"
|
||||
return mount.name or str(mount)
|
||||
|
||||
|
||||
@@ -11740,14 +11903,22 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]:
|
||||
if action == "another":
|
||||
return _push(state, "platforms", _platform_focus(state, "retroarch"))
|
||||
plat, path, _label = state.setups[row.value]
|
||||
# A setup found here is its folder: a card looked at and backed
|
||||
# out of described the RetroArch chosen next as that card.
|
||||
state.card_bound = None
|
||||
return _choose(state, plat, path)
|
||||
if screen == "platforms":
|
||||
plat, system = row.value if action == "cardsystem" else (row.value, row.value)
|
||||
state.platform = plat
|
||||
state.card_bound = card_bound(system, plat)
|
||||
if state.dest_given is not None:
|
||||
return _choose(state, row.value, state.dest_given)
|
||||
state.platform = row.value
|
||||
state.card_bound = card_bound(row.value, row.value)
|
||||
return _choose_card(state, state.dest_given)
|
||||
if state.card_bound:
|
||||
return _open_folder_screen(state, None)
|
||||
# On the device itself, the folder detection found there: a
|
||||
# KNULLI was asked for its own card.
|
||||
found = next((path for name, path, _label in state.setups if name == plat
|
||||
and detected_os(name, path) == state.card_bound[0]), None)
|
||||
return _open_folder_screen(state, found, "found" if found else "usual")
|
||||
# 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.
|
||||
@@ -11758,9 +11929,16 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]:
|
||||
if screen == "folder":
|
||||
if action == "default":
|
||||
return _choose(state, state.platform, state.folder_default)
|
||||
if action == "card":
|
||||
below = (_tui_card_bound(state) or ("", ""))[1]
|
||||
return _choose(state, state.platform, row.value / below if below else row.value)
|
||||
if action in ("card", "rescan"):
|
||||
if action == "rescan":
|
||||
state.cards = mounted_cards_of(_tui_card_bound(state)[0], state.platform)
|
||||
cards = [index for index, item in enumerate(tui_rows(state))
|
||||
if item.action == "card"]
|
||||
state.focus["folder"] = cards[0] if cards else state.focus.get("folder", 0)
|
||||
state.status = ("" if cards else f"Still no {_tui_card_bound(state)[0]} card "
|
||||
"is mounted.")
|
||||
return []
|
||||
return _choose_card(state, row.value)
|
||||
if action == "browse":
|
||||
state.browse_for = "dest"
|
||||
_push(state, "browse")
|
||||
@@ -11784,7 +11962,7 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]:
|
||||
# The check runs again with it: the plan then says what it holds.
|
||||
return _start_check(state)
|
||||
if action == "use":
|
||||
return _choose(state, state.platform, state.browse_dir)
|
||||
return _choose_card(state, state.browse_dir)
|
||||
if action == "up" and state.browse_dir is not None:
|
||||
if row.value == state.browse_dir:
|
||||
_browse_to(state, None if os.name == "nt" else state.browse_dir)
|
||||
@@ -12147,8 +12325,12 @@ def _path_key(state: TuiState, key: TuiKey) -> list[tuple]:
|
||||
if closed is not None:
|
||||
state.status = _closed_status(state, closed)
|
||||
return []
|
||||
refused = _card_refusal(state, path)
|
||||
if refused:
|
||||
state.status = refused
|
||||
return []
|
||||
state.stack.pop()
|
||||
return _choose(state, state.platform, path)
|
||||
return _choose_card(state, path)
|
||||
return []
|
||||
|
||||
|
||||
@@ -12889,12 +13071,22 @@ def _view_platforms(state: TuiState, width: int, height: int) -> tuple:
|
||||
def _view_folder(state: TuiState, width: int, height: int) -> tuple:
|
||||
name = display_name(state.platform)
|
||||
bound = _tui_card_bound(state)
|
||||
if bound:
|
||||
if bound and state.folder_default is not None and state.folder_kind == "found":
|
||||
# On the device itself: its own folder, which is no card to look for.
|
||||
title = "Which folder?"
|
||||
before = [f"{bound[0]} reads its BIOS files from:", str(state.folder_default)]
|
||||
elif bound:
|
||||
title = "Where is the SD card?"
|
||||
before = [f"{bound[0]} keeps its BIOS files on its SD card. Choose the card's "
|
||||
"root folder"
|
||||
+ (", the one holding .system." if state.platform in CARD_PLATFORMS
|
||||
else ".")]
|
||||
if state.folder_default is not None:
|
||||
# Use this folder takes it: named, not left to guess.
|
||||
before += [f"The BIOS folder chosen for {bound[0]}:", str(state.folder_default)]
|
||||
if any(row.action == "rescan" for row in tui_rows(state)):
|
||||
before.append(f"No {bound[0]} card is mounted. Insert it, then choose "
|
||||
"Look again.")
|
||||
elif state.folder_default is not None:
|
||||
title = "Which folder?"
|
||||
lead = {"found": f"{name} reads its BIOS files from:",
|
||||
@@ -13707,7 +13899,17 @@ def tui_start(
|
||||
if line.strip() and line.strip() not in unsaid]
|
||||
if plat and args.dest is not None:
|
||||
state.stack = []
|
||||
return state, _choose(state, plat, Path(args.dest))
|
||||
state.platform, state.card_bound = plat, card_bound(args.platform, plat)
|
||||
dest = Path(args.dest)
|
||||
if state.card_bound is None or plat in CARD_PLATFORMS:
|
||||
return state, _choose(state, plat, dest)
|
||||
# Read as the line mode reads --dest: a muOS card named for Onion
|
||||
# gets MUOS/bios, a folder that is no Onion card is refused.
|
||||
choice = card_choice(args.platform, plat, dest)
|
||||
if choice.refused:
|
||||
state.status = f"{choice.refused}. Choose the root of the {state.card_bound[0]} card."
|
||||
return state, _open_folder_screen(state, None)
|
||||
return state, _choose(state, choice.plat, choice.folder)
|
||||
if plat:
|
||||
bound = card_bound(args.platform, plat)
|
||||
# A system that runs from its card is its card or the device, never
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ esac
|
||||
TEMP_INSTALLER=""
|
||||
TEMP_DIRECTORY=""
|
||||
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
|
||||
DEFAULT_INSTALL_SHA256="000b7e9e965f6d81c254d7ee7967077f9bbae8aca2286179f31d4f4f7b555d06"
|
||||
DEFAULT_INSTALL_SHA256="8833256124ffdfa38865e7b065f2720ec20997e1a91ba340eab4067e31b20727"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
{
|
||||
"dest": "roms/ports/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 636292,
|
||||
"sha256": "000b7e9e965f6d81c254d7ee7967077f9bbae8aca2286179f31d4f4f7b555d06",
|
||||
"size": 646794,
|
||||
"sha256": "8833256124ffdfa38865e7b065f2720ec20997e1a91ba340eab4067e31b20727",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -71,8 +71,8 @@
|
||||
"muos": {
|
||||
"archive": {
|
||||
"dest": "ARCHIVE/RetroBIOS.muxapp",
|
||||
"size": 688841,
|
||||
"sha256": "65c0ff1debcc384c1a6da526e4c92505a1628036cc4c7f51928d5253fbd07484"
|
||||
"size": 699343,
|
||||
"sha256": "42226d45d6a31c86b726b1af321e043f9894f9993d8c6de0f16cc84c04a2a1ed"
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
@@ -92,8 +92,8 @@
|
||||
{
|
||||
"name": "RetroBIOS/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 636292,
|
||||
"sha256": "000b7e9e965f6d81c254d7ee7967077f9bbae8aca2286179f31d4f4f7b555d06",
|
||||
"size": 646794,
|
||||
"sha256": "8833256124ffdfa38865e7b065f2720ec20997e1a91ba340eab4067e31b20727",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -166,8 +166,8 @@
|
||||
{
|
||||
"dest": ".config/modules/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 636292,
|
||||
"sha256": "000b7e9e965f6d81c254d7ee7967077f9bbae8aca2286179f31d4f4f7b555d06",
|
||||
"size": 646794,
|
||||
"sha256": "8833256124ffdfa38865e7b065f2720ec20997e1a91ba340eab4067e31b20727",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -2,11 +2,11 @@
|
||||
|
||||
NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the one holding
|
||||
.system.
|
||||
No NextUI card is mounted. Insert it, then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -3,10 +3,10 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
A/Enter Browse B/Esc Back ? Help
|
||||
+3
-3
@@ -3,12 +3,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -2,11 +2,11 @@
|
||||
|
||||
NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the
|
||||
one holding .system.
|
||||
No NextUI card is mounted. Insert it, then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+8
-8
@@ -166,12 +166,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
A Browse B Back
|
||||
=== folder-card 40x15
|
||||
RetroBIOS: Where is the SD card?
|
||||
@@ -179,12 +179,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
@@ -194,11 +194,11 @@
|
||||
|
||||
NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the
|
||||
one holding .system.
|
||||
No NextUI card is mounted. Insert it, then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
|
||||
+8
-8
@@ -166,12 +166,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
A Browse B Back
|
||||
=== folder-card 40x15
|
||||
RetroBIOS: Where is the SD card?
|
||||
@@ -179,12 +179,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
@@ -194,11 +194,11 @@
|
||||
|
||||
NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the
|
||||
one holding .system.
|
||||
No NextUI card is mounted. Insert it, then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
|
||||
+8
-8
@@ -166,12 +166,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
A Browse B Back
|
||||
=== folder-card 40x15
|
||||
RetroBIOS: Where is the SD card?
|
||||
@@ -179,12 +179,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
@@ -194,11 +194,11 @@
|
||||
|
||||
NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the
|
||||
one holding .system.
|
||||
No NextUI card is mounted. Insert it, then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+8
-8
@@ -166,12 +166,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
Enter Browse Esc Back ? Help
|
||||
=== folder-card 40x15
|
||||
RetroBIOS: Where is the SD card?
|
||||
@@ -179,12 +179,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
@@ -194,11 +194,11 @@
|
||||
|
||||
NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the
|
||||
one holding .system.
|
||||
No NextUI card is mounted. Insert it, then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+8
-8
@@ -166,12 +166,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
A Browse R2 Back
|
||||
=== folder-card 40x15
|
||||
RetroBIOS: Where is the SD card?
|
||||
@@ -179,12 +179,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
@@ -194,11 +194,11 @@
|
||||
|
||||
NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the
|
||||
one holding .system.
|
||||
No NextUI card is mounted. Insert it, then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+8
-8
@@ -166,12 +166,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
Start Browse B Back
|
||||
=== folder-card 40x15
|
||||
RetroBIOS: Where is the SD card?
|
||||
@@ -179,12 +179,12 @@
|
||||
NextUI keeps its BIOS files on its SD
|
||||
card. Choose the card's root folder,
|
||||
the one holding .system.
|
||||
No NextUI card is mounted. Insert it,
|
||||
then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
@@ -194,11 +194,11 @@
|
||||
|
||||
NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the
|
||||
one holding .system.
|
||||
No NextUI card is mounted. Insert it, then choose Look again.
|
||||
|
||||
> Browse for a folder
|
||||
Type a path
|
||||
|
||||
|
||||
Look again
|
||||
|
||||
|
||||
|
||||
|
||||
+288
-14
@@ -40,6 +40,7 @@ sys.path.insert(0, str(REPO_ROOT))
|
||||
sys.path.insert(0, str(REPO_ROOT / "tests"))
|
||||
|
||||
import install # noqa: E402
|
||||
from test_install_app import make_card # noqa: E402
|
||||
from test_install_selection import FakeRepository, _QuietHandler # noqa: E402
|
||||
|
||||
try:
|
||||
@@ -1294,6 +1295,20 @@ class ControlCharacters(unittest.TestCase):
|
||||
|
||||
|
||||
class SmallFixes(unittest.TestCase):
|
||||
def test_a_system_name_is_said_once_with_a_pack(self):
|
||||
"""tui_audit_5 P3-8.1: "Onion gets the RetroArch file list" was
|
||||
printed by the pack check, then again where the platform is read."""
|
||||
pack = mock.Mock(files=[Path("RetroArch_BIOS_Pack.zip")])
|
||||
pack.name = "RetroArch_BIOS_Pack.zip"
|
||||
pack.platforms.return_value = ["retroarch"]
|
||||
args = argparse.Namespace(manifest=None, from_pack=Path("p.zip"), platform="onion",
|
||||
**{"from": None})
|
||||
with mock.patch.object(install, "PackSource", return_value=pack), \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
install._open_local_sources(args)
|
||||
self.assertNotIn("Onion gets", out.getvalue())
|
||||
install.LOCAL.reset()
|
||||
|
||||
def test_a_command_line_region_the_list_lacks(self):
|
||||
state = checked_state()
|
||||
state.cli_regions = ["europe"]
|
||||
@@ -1727,7 +1742,8 @@ class AuditRoundTwo(unittest.TestCase):
|
||||
state = fresh_state()
|
||||
state.stack = ["platforms"]
|
||||
press(state, "/", "m", "u", "o", "s")
|
||||
self.assertEqual([row.value for row in install.tui_rows(state)][1:], ["retroarch"])
|
||||
self.assertEqual([row.value for row in install.tui_rows(state)][1:],
|
||||
[("retroarch", "muOS")])
|
||||
|
||||
def test_p2_13_a_long_preview_wraps_only_what_is_in_view(self):
|
||||
state = checked_state()
|
||||
@@ -2130,6 +2146,11 @@ class AuditRoundThree(unittest.TestCase):
|
||||
def test_p1_1_a_drive_letter_names_the_card(self):
|
||||
self.assertEqual(install._mount_name(PureWindowsPath("E:\\")), "E:\\")
|
||||
self.assertEqual(install._mount_name(Path("/media/me/MUOS")), "MUOS")
|
||||
# tui_audit_5 P3-2: under WSL the card screen listed "d (card or drive)".
|
||||
with mock.patch.dict(install._WSL, {"active": True}):
|
||||
self.assertEqual(install._mount_name(Path("/mnt/d")), "D:")
|
||||
self.assertEqual(install._mount_name(Path("/mnt/wslg")), "wslg")
|
||||
self.assertEqual(install._mount_name(Path("/mnt/d")), "d")
|
||||
|
||||
def test_p1_1_on_the_device_its_card_is_not_offered_twice(self):
|
||||
root = self.card(self.CARDS["muOS"][0])
|
||||
@@ -2849,23 +2870,201 @@ class ACardSystemNamedOnAComputer(unittest.TestCase):
|
||||
self.assertEqual(install.card_install_dest("knulli", "knulli", True),
|
||||
knulli / "bios")
|
||||
|
||||
def test_the_interface_opens_the_card_screen_with_the_cards_first(self):
|
||||
def ask(self, *answers: str):
|
||||
"""_ask answering in turn, and the prompts it was asked."""
|
||||
given = iter(answers)
|
||||
asked: list = []
|
||||
|
||||
def answer(prompt: str) -> str:
|
||||
asked.append(prompt)
|
||||
return next(given)
|
||||
return mock.patch.object(install, "_ask", side_effect=answer), asked
|
||||
|
||||
def test_a_typed_root_that_does_not_exist_is_asked_again(self):
|
||||
"""code_audit_12 P2-3: a typo four folders deep was created and
|
||||
filled, and a menu answer (8) became the card."""
|
||||
onion = self.card(self.ONION)
|
||||
typo = REPO_ROOT / "tmp" / "no-such" / "media" / "NEXT"
|
||||
patcher, asked = self.ask("8", str(typo), str(onion))
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]), patcher, \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
dest = install.card_install_dest("onion", "retroarch", interactive=True)
|
||||
self.assertEqual(dest, onion / "BIOS")
|
||||
self.assertEqual(len(asked), 3)
|
||||
self.assertIn("8 does not exist, or is not a folder. Type the root of the Onion card.",
|
||||
out.getvalue())
|
||||
self.assertFalse((REPO_ROOT / "tmp" / "no-such").exists())
|
||||
|
||||
def test_a_typed_root_that_is_no_card_of_that_system_is_asked_again(self):
|
||||
"""code_audit_12 P3-4: a folder that was no Onion card ended the run."""
|
||||
blank, onion = self.card({"roms/a": ""}), self.card(self.ONION)
|
||||
patcher, asked = self.ask(str(blank), str(onion))
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]), patcher, \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
dest = install.card_install_dest("onion", "retroarch", interactive=True)
|
||||
self.assertEqual((dest, len(asked)), (onion / "BIOS", 2))
|
||||
self.assertIn(f"{blank} is not a Onion card: it holds no .tmp_update.", out.getvalue())
|
||||
|
||||
def test_a_minui_root_is_a_minui_card(self):
|
||||
blank = self.card({"roms/a": ""})
|
||||
(blank / "BIOS").mkdir()
|
||||
minui = self.card({".system/version.txt": "NextUI-20250101\n", "Bios/x": ""})
|
||||
patcher, asked = self.ask(str(minui / "Bios"), str(blank), str(minui))
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]), patcher, \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
dest = install.card_install_dest("nextui", "nextui", interactive=True)
|
||||
self.assertEqual((dest, len(asked)), (minui, 3))
|
||||
self.assertIn(f"is inside a NextUI card, whose root is {minui}", out.getvalue())
|
||||
self.assertIn("holds no .system/version.txt: it is not a NextUI card", out.getvalue())
|
||||
|
||||
def test_the_mounted_drives_are_listed_and_taken_by_number(self):
|
||||
"""tui_audit_5 P3-8.4: the screen lists the mounted cards, the
|
||||
prompt did not."""
|
||||
muos, blank = self.card({"MUOS/bios/x": ""}), self.card({"roms/a": ""})
|
||||
patcher, _asked = self.ask("2")
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[muos, blank]), \
|
||||
patcher, contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
dest = install.card_install_dest("knulli", "knulli", interactive=True)
|
||||
self.assertEqual(dest, blank / "bios")
|
||||
self.assertIn(f"2) {blank} (card or drive)", out.getvalue())
|
||||
self.assertIn(f"1) {muos} (muOS card)", out.getvalue())
|
||||
|
||||
def test_creating_several_folders_on_the_card_is_asked(self):
|
||||
root = self.card({"roms/a.txt": ""})
|
||||
rocknix = root / "R"
|
||||
rocknix.mkdir()
|
||||
patcher, asked = self.ask(str(rocknix), "n")
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]), patcher, \
|
||||
contextlib.redirect_stdout(io.StringIO()), \
|
||||
self.assertRaises(SystemExit) as stop:
|
||||
install.card_install_dest("rocknix", "rocknix", interactive=True)
|
||||
self.assertEqual(stop.exception.code, 2)
|
||||
self.assertIn("[y/N]", asked[-1])
|
||||
self.assertFalse((rocknix / "roms").exists())
|
||||
|
||||
def test_the_menu_asks_a_card_system_for_its_card(self):
|
||||
"""code_audit_12 P3-3: ROCKNIX picked from the menu on a PC was
|
||||
offered /storage/roms/bios."""
|
||||
card = self.card({"roms/a": ""})
|
||||
make_card(card, "ROCKNIX")
|
||||
patcher, asked = self.ask(str(card))
|
||||
with mock.patch.object(install, "_choose_platform", return_value="rocknix"), \
|
||||
mock.patch.object(install, "_mounted_cards", return_value=[]), patcher, \
|
||||
contextlib.redirect_stdout(io.StringIO()):
|
||||
chosen = install._prompt_manual_platform("linux")
|
||||
self.assertEqual(chosen, [("rocknix", card / "roms" / "bios")])
|
||||
self.assertEqual(asked, ["Card root (Ctrl+C to stop): "])
|
||||
|
||||
def test_a_card_of_another_system_is_not_offered(self):
|
||||
"""tui_audit_5 P1-1: A on the first mount wrote Onion's BIOS/ on a
|
||||
muOS card, which reads MUOS/bios."""
|
||||
muos, blank = self.card({"MUOS/bios/x": ""}), self.card({"roms/a": ""})
|
||||
found = [("retroarch", SYSTEM_DIR)]
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[blank]), \
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[muos, blank]), \
|
||||
mock.patch.object(install, "detect_platforms", return_value=found), \
|
||||
mock.patch.object(install, "detect_frontends", return_value=[]):
|
||||
state, effects = install.tui_start(_args(platform="onion"), "linux")
|
||||
self.assertEqual((state.screen, effects), ("folder", []))
|
||||
rows = install.tui_rows(state)
|
||||
text = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
|
||||
effects = install.tui_update(state, Key("enter"))
|
||||
self.assertEqual([row.action for row in rows], ["card", "browse", "type"])
|
||||
self.assertEqual([row.action for row in rows], ["browse", "type", "rescan"])
|
||||
self.assertEqual(state.focus["folder"], 0)
|
||||
self.assertIn("Where is the SD card?", text)
|
||||
self.assertIn("Onion keeps its BIOS files on its SD card", text)
|
||||
self.assertEqual(effects, [("fetch", "retroarch")])
|
||||
self.assertEqual(state.dest, blank / "BIOS")
|
||||
self.assertIn("No Onion card is mounted. Insert it, then choose Look again.", text)
|
||||
|
||||
def test_only_the_cards_of_that_system_are_listed(self):
|
||||
first, second = self.card(self.ONION), self.card(self.ONION)
|
||||
muos = self.card({"MUOS/bios/x": ""})
|
||||
state = fresh_state()
|
||||
state.stack = ["setup", "platforms"]
|
||||
state.filters["platforms"] = "onion"
|
||||
state.focus["platforms"] = 1
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[muos, first, second]):
|
||||
press(state, "enter")
|
||||
self.assertEqual(state.screen, "folder")
|
||||
rows = install.tui_rows(state)
|
||||
install.tui_update(state, Key("enter"))
|
||||
self.assertEqual([(row.action, row.value) for row in rows][:2],
|
||||
[("card", first), ("card", second)])
|
||||
self.assertNotIn("rescan", [row.action for row in rows])
|
||||
self.assertEqual(state.dest, first / "BIOS")
|
||||
# Titled as the card found by itself (tui_audit_5 P3-7).
|
||||
self.assertEqual(install.detected_os("retroarch", first / "BIOS"), "Onion")
|
||||
|
||||
def test_look_again_finds_the_card_inserted(self):
|
||||
onion = self.card(self.ONION)
|
||||
mounts: list = []
|
||||
with mock.patch.object(install, "_mounted_cards", side_effect=lambda: list(mounts)), \
|
||||
mock.patch.object(install, "detect_platforms", return_value=[]), \
|
||||
mock.patch.object(install, "detect_frontends", return_value=[]):
|
||||
state, _effects = install.tui_start(_args(platform="onion"), "linux")
|
||||
press(state, "down", "down", "enter")
|
||||
self.assertEqual(state.status, "Still no Onion card is mounted.")
|
||||
mounts.append(onion)
|
||||
press(state, "enter")
|
||||
rows = install.tui_rows(state)
|
||||
self.assertEqual(rows[state.focus["folder"]].action, "card")
|
||||
self.assertEqual(state.status, "")
|
||||
|
||||
def test_a_folder_that_is_no_card_of_that_system_is_refused(self):
|
||||
blank = self.card({"roms/a": ""})
|
||||
muos = self.card({"MUOS/bios/x": ""})
|
||||
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=[]):
|
||||
state, _effects = install.tui_start(_args(platform="onion"), "linux")
|
||||
press(state, "enter")
|
||||
self.assertEqual(state.screen, "browse")
|
||||
install._browse_to(state, blank)
|
||||
effects = press(state, "enter")
|
||||
self.assertEqual((effects, state.screen), ([], "browse"))
|
||||
self.assertIn(f"{blank} is not a Onion card: it holds no .tmp_update.",
|
||||
state.status)
|
||||
self.assertIsNone(state.dest)
|
||||
# A muOS card gets the folder muOS reads, as --dest does.
|
||||
install._browse_to(state, muos)
|
||||
press(state, "enter")
|
||||
self.assertEqual(state.dest, muos / "MUOS" / "bios")
|
||||
|
||||
def test_a_typed_card_root_is_checked_on_the_path_screen(self):
|
||||
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=[]):
|
||||
state, _effects = install.tui_start(_args(platform="rocknix"), "linux")
|
||||
press(state, "down", "enter")
|
||||
self.assertEqual(state.screen, "path")
|
||||
state.path_text = str(REPO_ROOT / "tmp" / "no-such-card")
|
||||
effects = press(state, "enter")
|
||||
self.assertEqual((effects, state.screen), ([], "path"))
|
||||
self.assertIn("does not exist, or is not a folder. Choose the root of the "
|
||||
"ROCKNIX card.", state.status)
|
||||
|
||||
def test_a_dest_that_is_no_card_of_that_system_is_refused(self):
|
||||
blank = self.card({"roms/a": ""})
|
||||
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=[]):
|
||||
state, effects = install.tui_start(_args(platform="onion", dest=str(blank)),
|
||||
"linux")
|
||||
self.assertEqual((effects, state.screen), ([], "folder"))
|
||||
self.assertIn("is not a Onion card", state.status)
|
||||
|
||||
def test_change_folder_names_the_folder_it_reuses(self):
|
||||
"""code_audit_12 P3-1: Use this folder on the card screen named
|
||||
nothing."""
|
||||
onion = self.card(self.ONION)
|
||||
state = checked_state()
|
||||
state.platform, state.card_bound = "retroarch", ("Onion", "BIOS")
|
||||
state.dest = onion / "BIOS"
|
||||
press(state, "o")
|
||||
rows = install.tui_rows(state)
|
||||
state.focus["options"] = [row.action for row in rows].index("folder")
|
||||
press(state, "enter")
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]):
|
||||
text = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
|
||||
self.assertEqual(state.screen, "folder")
|
||||
self.assertIn("Where is the SD card?", text)
|
||||
self.assertIn("The BIOS folder chosen for Onion:", text)
|
||||
self.assertEqual(state.folder_default, onion / "BIOS")
|
||||
|
||||
def test_the_interface_takes_the_one_onion_card_mounted(self):
|
||||
onion = self.card(self.ONION)
|
||||
@@ -2914,17 +3113,22 @@ class TheSetupFilterKnowsDevices(unittest.TestCase):
|
||||
state = fresh_state()
|
||||
state.stack = ["platforms"]
|
||||
state.filters["platforms"] = needle
|
||||
return [row for row in install.tui_rows(state) if row.action == "platform"]
|
||||
return [row for row in install.tui_rows(state)
|
||||
if row.action in ("platform", "cardsystem")]
|
||||
|
||||
def test_each_device_finds_every_setup_it_runs(self):
|
||||
for needle, setups in self.TYPED.items():
|
||||
with self.subTest(needle=needle):
|
||||
self.assertEqual({row.value for row in self.rows(needle)}, setups)
|
||||
self.assertEqual({row.value[0] if row.action == "cardsystem" else row.value
|
||||
for row in self.rows(needle)}, setups)
|
||||
|
||||
def test_the_right_column_names_what_matched(self):
|
||||
retroarch = {row.value: row for row in self.rows("miyoo")}["retroarch"]
|
||||
self.assertEqual(retroarch.right.split(", "),
|
||||
["spruceOS", "Onion", "Allium", "Koriki", "MiyooCFW"])
|
||||
rows = self.rows("miyoo")
|
||||
retroarch = {row.value: row for row in rows}["retroarch"]
|
||||
self.assertEqual(retroarch.right, "MiyooCFW")
|
||||
self.assertEqual([row.label for row in rows if row.action == "cardsystem"],
|
||||
["spruceOS, SD card", "Onion, SD card", "Allium, SD card",
|
||||
"Koriki, SD card"])
|
||||
deck = {row.value: row for row in self.rows("steam deck")}
|
||||
self.assertEqual(deck["retroarch"].right, "Steam Deck")
|
||||
self.assertIn("muOS", {row.value: row for row in self.rows("")}["retroarch"].right)
|
||||
@@ -2943,6 +3147,76 @@ class TheSetupFilterKnowsDevices(unittest.TestCase):
|
||||
tuple(str(name) for name in system["devices"]))
|
||||
|
||||
|
||||
class ACardSetupIsOneDesign(unittest.TestCase):
|
||||
"""code_audit_12 P2-1 to P3-2, tui_audit_5 P1-2: a setup that runs from
|
||||
its SD card, chosen on a computer, always asks for that card."""
|
||||
|
||||
def platforms(self, needle: str = "") -> install.TuiState:
|
||||
state = fresh_state()
|
||||
state.stack = ["setup", "platforms"]
|
||||
state.filters["platforms"] = needle
|
||||
state.focus["platforms"] = 1
|
||||
return state
|
||||
|
||||
def test_a_handheld_typed_in_the_filter_asks_for_its_card(self):
|
||||
for needle in ("onion", "miyoo mini", "rg35xx"):
|
||||
with self.subTest(needle=needle):
|
||||
state = self.platforms(needle)
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]):
|
||||
row = install.tui_rows(state)[1]
|
||||
effects = press(state, "enter")
|
||||
text = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
|
||||
self.assertEqual(row.action, "cardsystem")
|
||||
self.assertEqual((effects, state.screen), ([], "folder"))
|
||||
self.assertEqual(state.card_bound, install.card_bound(row.value[1], "retroarch"))
|
||||
self.assertIn("Where is the SD card?", text)
|
||||
self.assertNotIn("default", [r.action for r in install.tui_rows(state)])
|
||||
self.assertNotIn(str(SYSTEM_DIR), text)
|
||||
|
||||
def test_retroarch_typed_is_still_this_computer(self):
|
||||
state = self.platforms("retroarch")
|
||||
press(state, "enter")
|
||||
self.assertIsNone(state.card_bound)
|
||||
self.assertEqual(state.folder_default, SYSTEM_DIR)
|
||||
|
||||
def test_a_card_backed_out_of_is_forgotten(self):
|
||||
state = fresh_state()
|
||||
state.stack = ["setup"]
|
||||
state.focus["setup"] = 2
|
||||
press(state, "enter")
|
||||
state.focus["platforms"] = install._platform_focus(state, "rocknix")
|
||||
with mock.patch.object(install, "_mounted_cards", return_value=[]):
|
||||
press(state, "enter")
|
||||
self.assertEqual(state.card_bound, ("ROCKNIX", "roms/bios"))
|
||||
press(state, "esc", "esc")
|
||||
self.assertEqual(state.screen, "setup")
|
||||
state.focus["setup"] = 0
|
||||
with mock.patch.object(install.Path, "exists", return_value=True):
|
||||
press(state, "enter")
|
||||
self.assertIsNone(state.card_bound)
|
||||
self.assertEqual((state.platform, state.dest), ("retroarch", SYSTEM_DIR))
|
||||
install._open_folder_screen(state, state.dest, "chosen")
|
||||
title = install.tui_view(state, 80, 24)[0]
|
||||
self.assertIn("Which folder?", title)
|
||||
self.assertNotIn("card", [row.action for row in install.tui_rows(state)])
|
||||
|
||||
def test_on_the_device_its_own_folder_is_offered(self):
|
||||
share = Path("/userdata/bios")
|
||||
state = fresh_state()
|
||||
state.setups = [("knulli", share, "KNULLI")]
|
||||
state.stack = ["setup", "platforms"]
|
||||
state.focus["platforms"] = install._platform_focus(state, "knulli")
|
||||
with mock.patch.dict(install._DETECTED_OS, {("knulli", share): "KNULLI"}), \
|
||||
mock.patch.object(install, "_mounted_cards", return_value=[]):
|
||||
press(state, "enter")
|
||||
rows = [row.action for row in install.tui_rows(state)]
|
||||
text = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
|
||||
self.assertEqual(rows, ["default", "browse", "type"])
|
||||
self.assertEqual((state.folder_default, state.folder_kind), (share, "found"))
|
||||
self.assertIn("Which folder? KNULLI reads its BIOS files from: /userdata/bios", text)
|
||||
self.assertNotIn("No KNULLI card is mounted", text)
|
||||
|
||||
|
||||
class OneSystemInFewPresses(unittest.TestCase):
|
||||
"""tui_audit_4 P3-10.11: one system with a controller took 15 presses on
|
||||
the fixture list, about 60 on muxterm, which has no page key."""
|
||||
|
||||
Reference in new issue
Block a user