fix: assume no default where detection was refused

The Flatpak RetroArch's folder at 000 hid the retroarch.cfg naming ~/Games/bios, and the run offered 6.6 GB to ~/.config/retroarch/system. A closed folder under HOME or above the default now leaves no default: --dest is asked for. Python 3.14's quiet Path.exists() refusals are told from absence by os.stat, and an assumed folder reads as the one a frontend usually reads.
This commit is contained in:
Abdessamad Derraz committed 2026-10-24 02:58:32 +02:00
1 parent 00bb4aa0ba
commit 153ba479f6
14 files changed
+137 -55

No files matched your search

+1 -1
View File
@@ -11,7 +11,7 @@
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "bb0a0a88e16a6fb7aa93ec0a2b4cb5f1d667faacd50830aae9ce763e60d5d112"
$defaultInstallSha256 = "be9d37048e800072d6d5d2a40afaa5a46d5b062577f83c0fa633fc15159de66a"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+37 -15
View File
@@ -4254,10 +4254,20 @@ def _probed(probe: Callable[[], bool], path: Path) -> bool:
"""probe() for path, False where the place cannot be looked at. A
refusal met while looking for setups is said once (_note_refused)."""
try:
return probe()
found = probe()
except OSError as exc:
_note_refused(path, exc)
return False
if not found:
# Python 3.14's Path.exists() answers False where earlier ones
# raised: a refusal is told from absence by the stat it hides.
try:
os.stat(path)
except PermissionError as exc:
_note_refused(path, exc)
except OSError:
pass
return found
def _exists(path: Path) -> bool:
@@ -4284,7 +4294,7 @@ def closed_folder(path: Path) -> tuple[Path, str] | None:
first line said where.
"""
try:
path.exists()
os.stat(path)
return None
except OSError as exc:
failure = exc
@@ -6269,17 +6279,27 @@ def _default_dest(os_type: str, platform_name: str) -> Path | None:
C:\\userdata\\bios, reported done.
"""
if os_type == "android" and platform_name == "retroarch":
return _android_storage_root() / "RetroArch" / "system"
if platform_name in PORTABLE_ONLY:
default = _android_storage_root() / "RetroArch" / "system"
elif platform_name in PORTABLE_ONLY or (os_type == "windows" and platform_name
not in _os_default_dests(os_type)):
return None
own = _os_default_dests(os_type).get(platform_name)
if own is not None:
return own
if os_type == "windows":
else:
default = (_os_default_dests(os_type).get(platform_name)
or (None if platform_name == "retrobat" else DEFAULT_DESTS.get(platform_name)))
if default is not None and _refused_near(default):
# The Flatpak's own folder at 000 hid the retroarch.cfg naming
# ~/Games/bios, and 6.6 GB were offered to a folder it does not read.
return None
if platform_name == "retrobat":
return None
return DEFAULT_DESTS.get(platform_name)
return default
def _refused_near(default: Path) -> bool:
"""Whether detection met a closed folder under HOME, where configs that
could name another folder live, or above the default itself."""
home = Path.home()
place = default.absolute()
return any(home in (folder, *folder.parents) or folder in (place, *place.parents)
for folder, _why in _DETECTION_REFUSED)
def _manual_usage_hint(os_type: str, listing: str = "") -> list[str]:
@@ -9219,10 +9239,12 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
else:
default_dest = _default_dest(os_type, args.platform)
if default_dest is None:
where = ("where this user can look, and its default folder may not be "
"the one it reads" if _DETECTION_REFUSED
else "and no folder on this system is one it reads")
print(
f" Platform '{args.platform}' not found, and no folder on this "
f"system is one it reads. Name it with --dest, for example: "
f"--platform {args.platform} --dest <its BIOS folder>",
f" Platform '{args.platform}' not found {where}. Name it with "
f"--dest, for example: --platform {args.platform} --dest <its BIOS folder>",
file=sys.stderr,
)
sys.exit(1)
@@ -11986,7 +12008,7 @@ 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} reads its BIOS files from:", str(state.folder_default)]
before = [f"{name} usually reads its BIOS files from:", str(state.folder_default)]
else:
title = "Which folder?"
before = [f"No usual {name} folder here: browse for it or type it."]
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER=""
TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="bb0a0a88e16a6fb7aa93ec0a2b4cb5f1d667faacd50830aae9ce763e60d5d112"
DEFAULT_INSTALL_SHA256="be9d37048e800072d6d5d2a40afaa5a46d5b062577f83c0fa633fc15159de66a"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+1 -1
View File
@@ -1,6 +1,6 @@
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files from:
/home/user/.config/retroarch/system
> Use this folder
+2 -2
View File
@@ -1,6 +1,7 @@
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -8,5 +9,4 @@
Type a path
A Use this folder B Back ? Help
+2 -2
View File
@@ -1,6 +1,7 @@
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -11,5 +12,4 @@
A Use this folder B Back ? Help
+1 -1
View File
@@ -1,6 +1,6 @@
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files from:
/home/user/.config/retroarch/system
> Use this folder
+5 -5
View File
@@ -109,7 +109,8 @@
=== folder 40x12
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -117,12 +118,12 @@
Type a path
A Use this folder B Back
=== folder 40x15
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -133,12 +134,11 @@
A Use this folder B Back
=== folder 80x24
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files from:
/home/user/.config/retroarch/system
> Use this folder
+5 -5
View File
@@ -109,7 +109,8 @@
=== folder 40x12
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -117,12 +118,12 @@
Type a path
A Use this folder B Back
=== folder 40x15
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -133,12 +134,11 @@
A Use this folder B Back
=== folder 80x24
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files from:
/home/user/.config/retroarch/system
> Use this folder
+5 -5
View File
@@ -109,7 +109,8 @@
=== folder 40x12
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -117,12 +118,12 @@
Type a path
A Use this folder B Back
=== folder 40x15
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -133,12 +134,11 @@
A Use this folder B Back
=== folder 80x24
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files from:
/home/user/.config/retroarch/system
> Use this folder
+5 -5
View File
@@ -109,7 +109,8 @@
=== folder 40x12
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -117,12 +118,12 @@
Type a path
Enter Use this folder Esc Back
=== folder 40x15
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -133,12 +134,11 @@
Enter Use this folder Esc Back
=== folder 80x24
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files from:
/home/user/.config/retroarch/system
> Use this folder
+5 -5
View File
@@ -109,7 +109,8 @@
=== folder 40x12
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -117,12 +118,12 @@
Type a path
A Use this folder R2 Back
=== folder 40x15
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -133,12 +134,11 @@
A Use this folder R2 Back
=== folder 80x24
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files from:
/home/user/.config/retroarch/system
> Use this folder
+5 -5
View File
@@ -109,7 +109,8 @@
=== folder 40x12
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -117,12 +118,12 @@
Type a path
Start Use this folder B Back
=== folder 40x15
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files
from:
/home/user/.config/retroarch/system
> Use this folder
@@ -133,12 +134,11 @@
Start Use this folder B Back
=== folder 80x24
RetroBIOS: Which folder?
RetroArch reads its BIOS files from:
RetroArch usually reads its BIOS files from:
/home/user/.config/retroarch/system
> Use this folder
+62 -2
View File
@@ -13,6 +13,7 @@ import http.server
import io
import json
import os
import shutil
import subprocess
import sys
import tempfile
@@ -686,8 +687,67 @@ class AHomeThisUserCannotOpen(unittest.TestCase):
self.assertEqual(proc.returncode, 1, proc.stdout + proc.stderr)
said = " ".join(proc.stderr.split())
self.assertNotIn("the one detected here", said)
self.assertIn("than the one taken as RetroArch's default, RetroArch not being "
"found here (name another with --dest).", said)
# HOME closed, the default is not assumed at all: --dest is asked for.
self.assertIn("Platform 'retroarch' not found where this user can look", said)
self.assertNotIn("using default path", proc.stdout + proc.stderr)
class TheFrontendsOwnFolderClosed(unittest.TestCase):
"""The Flatpak RetroArch's folder at 000: its retroarch.cfg names
~/Games/bios, and the run took ~/.config/retroarch/system, a folder it
does not read, and offered 6.6 GB there. A folder detection could not
look in leaves no default to assume."""
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
"a folder without search permission is POSIX")
def test_no_default_is_assumed(self):
with FakeRepository() as repo:
home = repo.root / "home"
flatpak = home / ".var" / "app" / "org.libretro.RetroArch"
cfg = flatpak / "config" / "retroarch" / "retroarch.cfg"
cfg.parent.mkdir(parents=True)
cfg.write_text(f'system_directory = "{home / "Games" / "bios"}"\n')
env = {**repo.env(), "HOME": str(home), "RETROBIOS_OS": "linux"}
os.chmod(flatpak, 0o000)
try:
proc = subprocess.run(
[sys.executable, str(REPO_ROOT / "install.py"), "--platform", "retroarch",
"--dry-run", "--no-input"],
env=env, capture_output=True, text=True, timeout=60,
stdin=subprocess.DEVNULL)
finally:
os.chmod(flatpak, 0o755)
self.assertNotIn("Traceback", proc.stderr)
self.assertEqual(proc.returncode, 1, proc.stdout + proc.stderr)
self.assertNotIn("using default path", proc.stdout + proc.stderr)
self.assertIn(f"this user cannot open {flatpak}", proc.stderr)
self.assertIn("--dest", proc.stderr)
class ARefusalTheProbeDoesNotRaise(unittest.TestCase):
"""Python 3.14's Path.exists() answers False on a folder this user
cannot enter instead of raising: every closed-folder line went silent
and the run took the default again."""
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
"a folder without search permission is POSIX")
def test_a_quiet_false_is_still_told_from_absence(self):
base = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp"))
self.addCleanup(shutil.rmtree, base, True)
closed = base / "closed"
closed.mkdir()
os.chmod(closed, 0o000)
self.addCleanup(os.chmod, closed, 0o755)
target = closed / "retroarch.cfg"
install._DETECTION_REFUSED.clear()
self.addCleanup(install._DETECTION_REFUSED.clear)
quiet = mock.patch.object(install.Path, "exists", lambda self: False)
with quiet, mock.patch.object(install._SETUP_SEARCH, "active", True, create=True), \
contextlib.redirect_stderr(io.StringIO()):
self.assertFalse(install._exists(target))
self.assertFalse(install._exists(base / "absent"))
self.assertEqual(install.closed_folder(target), (closed, "Permission denied"))
self.assertEqual([folder for folder, _why in install._DETECTION_REFUSED], [closed])
class APartlyClosedHome(unittest.TestCase):