fix: skip data trees a read-only folder refuses

A BIOS folder this user cannot write no longer tries each data tree's archive and blames its URL with a raw errno. The tree is counted failed for the same cause as the BIOS files, so the summary keeps its one line and its advice.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 03:44:17 +01:00
1 parent f8702a19f7
commit 2ecbea56b5
5 files changed
+59 -13

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@
# and Invoke-WebRequest throws on a cut transfer before the hash is read:
# the site's build rewrites this line there (scripts/sitebootstrap.py).
$mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
$defaultInstallSha256 = "78ef177611168c1c7699cafa8982f901aaaf5342b6c55d6e158ba18fb4cc0ae2"
$defaultInstallSha256 = "c8cf4b65474279c30c2e1781028d3e05f195e26dd759a98a37db3477b6dd3014"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+22 -3
View File
@@ -7198,7 +7198,12 @@ def install_data_directory(entry: dict, bios_path: Path,
# Kept apart, so the advice is the offline one, not the network's.
counts["error"] = OfflineReason(str(exc))
except (urllib.error.URLError, OSError, ValueError, tarfile.TarError) as exc:
counts["error"] = str(getattr(exc, "reason", None) or exc)
# The tree's folder refused, not the source: said as the BIOS files
# say it, never as an archive that could not be read.
counts["error"] = (NOT_WRITABLE if isinstance(exc, OSError)
and not isinstance(exc, urllib.error.URLError)
and _write_refused(exc) and not _folder_writable(root)
else str(getattr(exc, "reason", None) or exc))
except _MEMBER_ERRORS as exc:
# A damaged, encrypted or unsupported member, in words.
counts["error"] = unreadable_words(exc)
@@ -15838,8 +15843,19 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
# PPSSPP's assets. Fetched from their source archive, each file
# checked against the hash the list records.
if data_dirs and not total_interrupted:
print("\nData directories:")
# A tree whose folder this user cannot write is not fetched: the
# BIOS files above already said the cause and its remedy, and
# each archive was named as unreadable.
writable = []
for entry, state in data_states:
if state[1] and not _folder_writable(state[0]):
failures.append((entry["ref"], NOT_WRITABLE))
total_errors += 1
else:
writable.append((entry, state))
if writable:
print("\nData directories:")
for entry, state in writable:
counts = install_data_directory(entry, bios_path, state)
total_downloaded += counts["installed"]
data_landed += counts["installed"]
@@ -15866,7 +15882,10 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
f", {counts['changed'] + counts['absent']} no longer in "
"the source (the pack has them)")
print(line)
if counts["error"]:
if counts["error"] == NOT_WRITABLE:
failures.append((entry["ref"], NOT_WRITABLE))
total_errors += 1
elif counts["error"]:
print(f" {entry['ref']}: could not read "
f"{LOCAL.pack.name if LOCAL.pack is not None else entry['url']}: "
f"{counts['error']}", file=sys.stderr)
+1 -1
View File
@@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins
# curl or wget stop on a cut transfer before the hash is read: the site's
# build rewrites this line there (scripts/sitebootstrap.py).
MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that."
DEFAULT_INSTALL_SHA256="78ef177611168c1c7699cafa8982f901aaaf5342b6c55d6e158ba18fb4cc0ae2"
DEFAULT_INSTALL_SHA256="c8cf4b65474279c30c2e1781028d3e05f195e26dd759a98a37db3477b6dd3014"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+8 -8
View File
@@ -27,8 +27,8 @@
{
"dest": "roms/ports/retrobios/install.py",
"repo_path": "install.py",
"size": 1100388,
"sha256": "78ef177611168c1c7699cafa8982f901aaaf5342b6c55d6e158ba18fb4cc0ae2",
"size": 1101457,
"sha256": "c8cf4b65474279c30c2e1781028d3e05f195e26dd759a98a37db3477b6dd3014",
"executable": false
},
{
@@ -71,8 +71,8 @@
"muos": {
"archive": {
"dest": "ARCHIVE/RetroBIOS.muxapp",
"size": 1154015,
"sha256": "03bcbdf8385808576afb0899860e5f11af37a9aee709b72481064e7a0ee79afa"
"size": 1155084,
"sha256": "41b72bf029de9d855312e472cc9a10046f71692a55e02c4a959efdf4201505ad"
},
"members": [
{
@@ -92,8 +92,8 @@
{
"name": "RetroBIOS/install.py",
"repo_path": "install.py",
"size": 1100388,
"sha256": "78ef177611168c1c7699cafa8982f901aaaf5342b6c55d6e158ba18fb4cc0ae2",
"size": 1101457,
"sha256": "c8cf4b65474279c30c2e1781028d3e05f195e26dd759a98a37db3477b6dd3014",
"executable": false
},
{
@@ -166,8 +166,8 @@
{
"dest": ".config/modules/retrobios/install.py",
"repo_path": "install.py",
"size": 1100388,
"sha256": "78ef177611168c1c7699cafa8982f901aaaf5342b6c55d6e158ba18fb4cc0ae2",
"size": 1101457,
"sha256": "c8cf4b65474279c30c2e1781028d3e05f195e26dd759a98a37db3477b6dd3014",
"executable": false
},
{
+27
View File
@@ -951,6 +951,33 @@ class OfflineLeavesFilesMissing(Scratch):
self.assertNotIn("could not read", proc.stderr)
self.assertNotIn("no longer in the source", proc.stdout)
@unittest.skipIf(os.name == "nt" or os.geteuid() == 0,
"a read-only folder is refused by POSIX permissions")
def test_a_tree_in_a_folder_not_writable_is_not_fetched(self):
"""A read-only card blamed each data archive's URL with a raw
errno, and that second cause split the one-line summary."""
listed = manifest()
listed["data_directories"] = [{
"ref": "bluemsx", "dest": "Machines", "type": "zip",
"url": "https://example.invalid/blueMSX.zip", "systems": ["nintendo-gba"],
"files": {"a.txt": {"sha1": _sha1(b"a"), "size": 1}},
}, {
"ref": "fbneo-cheats", "dest": "fbneo/cheats", "type": "zip",
"url": "https://example.invalid/cheats.zip", "systems": ["nintendo-gba"],
"files": {"b.txt": {"sha1": _sha1(b"b"), "size": 1}},
}]
self.list.write_text(json.dumps(listed))
self.complete()
self.dest.chmod(0o555)
self.addCleanup(self.dest.chmod, 0o755)
proc = self.run_installer("--manifest", str(self.list), "--no-input")
out = proc.stdout + proc.stderr
self.assertEqual(proc.returncode, 2, out)
self.assertNotIn("could not read", out)
self.assertNotIn("Errno", out)
self.assertNotIn("Data directories:", out)
self.assertIn("failed: 2 files (folder not writable), first bluemsx", out)
class OneOfflineVocabulary(Scratch):
"""--offline said one