fix: name the address a download was refused at

This commit is contained in:
Abdessamad Derraz committed 2026-11-05 19:31:42 +01:00
1 parent d78af1b00d
commit aaaafd5771
5 files changed
+92 -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 = "65f7d49de83de9d99734293009762adaff6f844c2c3627f507d4dc3b029f57be"
$defaultInstallSha256 = "fa8d130b046b1c1d1bc2e8da49905a7572d81ee95d023b483cdcf009a1d4a64c"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+34 -3
View File
@@ -5759,8 +5759,9 @@ def _download_one(
reason = f"HTTP {exc.code}"
if exc.code in (403, 429):
reason += " (GitHub rate limit or access refused)"
if exc.code >= 500 or exc.code == 429:
reason = NetworkReason(reason)
reason = (NetworkReason if exc.code >= 500 or exc.code == 429
else HttpReason)(reason)
reason.url = url
if verbose:
print(f" Attempt {attempt} failed: {exc}", file=sys.stderr)
if tmp_path is not None and partial is None and tmp_path.exists():
@@ -6264,6 +6265,32 @@ class NetworkReason(str):
"""A failure the connection caused: cut, timed out, refused by a server
or a proxy. The summary names the network instead of the folder."""
url = ""
class HttpReason(str):
"""A file the server answered for with an error of its own (HTTP 404):
the summary names the address, where "HTTP 404" alone told the user
neither what failed nor that running again changes nothing."""
url = ""
def _http_failure_lines(failures: list[tuple[str, str]]) -> list[str]:
"""The address the first refused file was asked at, and what chose it:
a mirror named with RETROBIOS_BASE_URL that holds the lists and not yet
the files, or a list out of step with the files it names."""
refused = [reason for _dest, reason in failures if isinstance(reason, HttpReason)]
if not refused:
return []
more = f" (and {_plural(len(refused) - 1, 'other file')})" if len(refused) > 1 else ""
lines = [f" The server answered {refused[0]} for {refused[0].url}{more}."]
if os.environ.get("RETROBIOS_BASE_URL"):
return lines + _address_lines(refused[0].url)[1:]
return lines + [" The file list names files the server does not hold, and running "
"again changes nothing until the two match.",
f" More help: {TROUBLESHOOTING_PAGE}"]
def _transfer_failure(exc: BaseException) -> bool:
"""Whether a download error came from the connection, not the disk."""
@@ -6409,8 +6436,12 @@ def _report_outcome(
if any(reason == READ_ONLY for _dest, reason in failures or []):
advice.append(" Files marked read-only are in the way: clear that attribute "
"(or delete them), then run again.")
advice += _http_failure_lines(failures or [])
if any(isinstance(reason, NetworkReason) for _dest, reason in failures or []):
advice.append(NETWORK_FAILURE_ADVICE)
if os.environ.get("RETROBIOS_BASE_URL"):
# A mirror that is down read as this machine's connection.
advice += _address_lines(BASE_URL)[1:]
if any(str(reason).startswith(NOT_KEPT) for _dest, reason in failures or []):
# The same run fails the same way: "retry only those" sent a phone
# user round the loop with nothing changed.
@@ -6472,7 +6503,7 @@ def _report_outcome(
if errors and not downloaded:
say(f"\nNothing was installed. {_plural(errors, 'file')} failed.")
if failures and all(isinstance(r, (NetworkReason, OfflineReason))
if failures and all(isinstance(r, (NetworkReason, HttpReason, OfflineReason))
for _d, r in failures):
# The folder was never the problem: naming it as the place being
# written to sent people looking at their disk.
+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="65f7d49de83de9d99734293009762adaff6f844c2c3627f507d4dc3b029f57be"
DEFAULT_INSTALL_SHA256="fa8d130b046b1c1d1bc2e8da49905a7572d81ee95d023b483cdcf009a1d4a64c"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+8 -8
View File
@@ -27,8 +27,8 @@
{
"dest": "roms/ports/retrobios/install.py",
"repo_path": "install.py",
"size": 1094917,
"sha256": "65f7d49de83de9d99734293009762adaff6f844c2c3627f507d4dc3b029f57be",
"size": 1096372,
"sha256": "fa8d130b046b1c1d1bc2e8da49905a7572d81ee95d023b483cdcf009a1d4a64c",
"executable": false
},
{
@@ -71,8 +71,8 @@
"muos": {
"archive": {
"dest": "ARCHIVE/RetroBIOS.muxapp",
"size": 1148544,
"sha256": "ba22f19af63c4003f7195636f6c7a5ebe3900ce6a300fe089058ff78eff52eb2"
"size": 1149999,
"sha256": "143e0067766ccb85f299ca125c1dbdaa5ebec15e85ea49a9e354a677fb7f931d"
},
"members": [
{
@@ -92,8 +92,8 @@
{
"name": "RetroBIOS/install.py",
"repo_path": "install.py",
"size": 1094917,
"sha256": "65f7d49de83de9d99734293009762adaff6f844c2c3627f507d4dc3b029f57be",
"size": 1096372,
"sha256": "fa8d130b046b1c1d1bc2e8da49905a7572d81ee95d023b483cdcf009a1d4a64c",
"executable": false
},
{
@@ -166,8 +166,8 @@
{
"dest": ".config/modules/retrobios/install.py",
"repo_path": "install.py",
"size": 1094917,
"sha256": "65f7d49de83de9d99734293009762adaff6f844c2c3627f507d4dc3b029f57be",
"size": 1096372,
"sha256": "fa8d130b046b1c1d1bc2e8da49905a7572d81ee95d023b483cdcf009a1d4a64c",
"executable": false
},
{
+48
View File
@@ -5160,6 +5160,54 @@ class ANetworkFailureNamesTheNetwork(unittest.TestCase):
self.assertNotIn("being written to", text)
class AnHttpErrorNamesItsAddress(unittest.TestCase):
"""A file the server answered 404 for ended on "HTTP 404" alone: no
address, no cause, and a mirror chosen with RETROBIOS_BASE_URL went
unnamed, as it did behind a NETWORK failure of that mirror."""
def _said(self, failures: list, base_url: str = "") -> str:
out = io.StringIO()
env = {"RETROBIOS_BASE_URL": base_url} if base_url else {}
with unittest.mock.patch.dict(os.environ, env), contextlib.redirect_stdout(out), \
self.assertRaises(SystemExit) as raised:
install._report_outcome([Path("/bios")], 0, 0, len(failures), 0, False,
failures=failures)
self.assertEqual(raised.exception.code, 2)
return " ".join(out.getvalue().split())
def _reason(self, kind: type, words: str, url: str) -> str:
reason = kind(words)
reason.url = url
return reason
def test_the_address_and_the_cause_are_said(self):
url = "https://raw.githubusercontent.com/Abdess/retrobios/main/bios/a.bin"
failures = [("a.bin", self._reason(install.HttpReason, "HTTP 404", url)),
("b.bin", self._reason(install.HttpReason, "HTTP 404", url + "x"))]
with unittest.mock.patch.dict(os.environ):
os.environ.pop("RETROBIOS_BASE_URL", None)
text = self._said(failures)
self.assertIn(f"The server answered HTTP 404 for {url} (and 1 other file).", text)
self.assertIn("running again changes nothing", text)
self.assertIn("BIOS folder: /bios", text)
self.assertNotIn("RETROBIOS_BASE_URL", text)
def test_a_mirror_named_in_the_environment_is_said(self):
mirror = "http://127.0.0.1:18766"
for kind, words in ((install.HttpReason, "HTTP 404"),
(install.NetworkReason, "the connection timed out")):
with self.subTest(kind=kind.__name__):
text = self._said([("a.bin", self._reason(kind, words, f"{mirror}/a.bin"))],
base_url=mirror)
self.assertIn("RETROBIOS_BASE_URL is set in this environment", text)
def test_the_download_records_the_address(self):
error = urllib.error.HTTPError("https://x/bios/a.bin", 404, "Not Found", None, None)
reason = ANetworkFailureNamesTheNetwork._fail(self, error)
self.assertIsInstance(reason, install.HttpReason)
self.assertTrue(reason.url.endswith("/bios/a.bin"), reason.url)
class RetroBatIsFoundWhereItSaysItIs(unittest.TestCase):
"""RetroBat on D:\\RetroBat was not detected, and --platform retrobat
wrote 7.6 GB into C:\\RetroBat\\bios, a folder it does not read.