diff --git a/esphome/espidf/framework.py b/esphome/espidf/framework.py index 13d99fd709..3418aa4597 100644 --- a/esphome/espidf/framework.py +++ b/esphome/espidf/framework.py @@ -748,8 +748,10 @@ def _prefetch_idf_tool_archives( ", ".join(entry["name"] for entry in entries), ) # tools.json always carries sizes; should one be missing the combined - # bar could not be trusted, so show no bar at all (per-file bars from - # several threads would interleave) rather than a wrong one. + # bar could not be trusted, so show no bar at all rather than a wrong + # one. Unlike the library prefetch there is no sequential fallback: + # per-file bars from several threads would interleave, and skipping + # the prefetch would lose the resume workaround for #17703. sizes = [entry.get("size") or 0 for entry in entries] progress = BatchDownloadProgress( "Downloading ESP-IDF tools", sum(sizes) if all(sizes) else 0 diff --git a/esphome/platformio/library.py b/esphome/platformio/library.py index 47c8d70fd6..d7e29d152e 100644 --- a/esphome/platformio/library.py +++ b/esphome/platformio/library.py @@ -900,8 +900,8 @@ def is_lib_ignored(name: str | None, lib_ignore: set[str]) -> bool: _DOWNLOAD_WORKERS = 4 -def _content_lengths(urls: list[str]) -> list[int]: - """Content-Length per URL via HEAD requests; 0 for any that fail.""" +def _content_lengths(urls: list[str]) -> list[int | None]: + """Content-Length per URL via HEAD requests; None when unknown.""" import requests def head(url: str) -> int | None: diff --git a/tests/unit_tests/test_platformio_library.py b/tests/unit_tests/test_platformio_library.py index b6f87bca88..f832d444cf 100644 --- a/tests/unit_tests/test_platformio_library.py +++ b/tests/unit_tests/test_platformio_library.py @@ -629,6 +629,24 @@ def test_prefetch_wave_downloads_registry_archives_in_parallel( ] +def test_prefetch_wave_unknown_size_falls_back_to_sequential( + setup_core, monkeypatch: pytest.MonkeyPatch +) -> None: + """Any unknown HEAD size skips the parallel prefetch entirely so the + sequential downloads keep their per-file bars.""" + + def fail_download(self, force=False, salt="", namespace="", progress=None): + raise AssertionError("prefetched despite unknown size") + + monkeypatch.setattr(ConvertedLibrary, "download", fail_download) + monkeypatch.setattr(lib, "_content_lengths", lambda urls: [1, None]) + wave = [ + ("a", ConvertedLibrary("a", "1.0", URLSource("https://x/a.tar.gz"))), + ("b", ConvertedLibrary("b", "1.0", URLSource("https://x/b.tar.gz"))), + ] + lib._prefetch_wave(wave, "", "idf") + + def test_content_lengths_head_requests(monkeypatch: pytest.MonkeyPatch) -> None: """Sizes come from HEAD Content-Length; a failing HEAD reads as 0 so the combined bar is skipped rather than wrong."""