diff --git a/esphome/espidf/framework.py b/esphome/espidf/framework.py index 39c036e6d7..5ea182379a 100644 --- a/esphome/espidf/framework.py +++ b/esphome/espidf/framework.py @@ -739,9 +739,8 @@ def _prefetch_idf_tool_archives( # seek/truncate writes; mirror the library prefetch's dedupe continue seen_dests.add(entry["dest"]) - # tools.json always carries sha256 and size; an entry missing - # either must not be downloaded unverified here, so leave it to - # the installer (which fails loudly on a bad archive). + # Never download unverified: an entry without sha256/size is + # left to the installer, which fails loudly on a bad archive if entry.get("sha256") and entry.get("size"): entries.append(entry) else: @@ -758,10 +757,8 @@ def _prefetch_idf_tool_archives( ", ".join(entry["name"] for entry in entries), ) - # Every entry carries a size (checked above), so the combined bar can - # be trusted. 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. + # No sequential fallback here: skipping the prefetch would lose the + # resume workaround for #17703, and every entry has a size (above) def _download(entry: dict): return lambda tracker: download_with_resume( entry["url"], diff --git a/esphome/framework_helpers.py b/esphome/framework_helpers.py index af8854af06..56a8a802d4 100644 --- a/esphome/framework_helpers.py +++ b/esphome/framework_helpers.py @@ -712,9 +712,8 @@ def _stream_response_to_file( (effective offset 0) discards the stale bytes. ``offset`` also seeds the progress bar so a resumed download shows overall progress. ``size`` is the known full file size; when None it is derived from the response's - content-length, and without either there is no progress bar. With - ``progress`` set, no bar is drawn here; the callback gets the absolute - byte count, seeded with ``offset`` and then after each chunk. + content-length, and without either there is no bar. With ``progress`` + set no bar is drawn here; the callback gets the absolute byte count. """ f.seek(offset) f.truncate(offset) @@ -748,16 +747,12 @@ def run_batch_downloads( jobs: list[tuple[str, Callable[[Callable[[int], None]], None]]], max_workers: int = BATCH_DOWNLOAD_WORKERS, ) -> list[tuple[str, Exception]]: - """Run download jobs concurrently, reporting into one combined bar. + """Run ``(name, fetch)`` download jobs concurrently under one combined bar. - ``jobs`` holds ``(name, fetch)`` pairs where ``fetch(tracker)`` performs - one download reporting absolute byte counts to ``tracker``. Failures are - collected (list.append is atomic under the GIL) and returned after the - bar is done, so the caller's warnings never land on the bar's row; a - failed job credits its tracker 0 so the bar can still complete. Ctrl-C - drops queued jobs and aborts in-flight ones at their next progress - tick; resumable ``.part`` files keep the bytes already fetched. - ``jobs`` must be non-empty. + Each ``fetch(tracker)`` reports absolute byte counts. Failures are + returned after the bar is done so warnings never land on its row. + Ctrl-C drops queued jobs and aborts in-flight ones at their next tick; + ``.part`` files keep the fetched bytes. ``jobs`` must be non-empty. """ failures: list[tuple[str, Exception]] = [] cancelled = threading.Event() @@ -798,14 +793,11 @@ class _BatchDownloadCancelled(Exception): class BatchDownloadProgress: - """One progress bar across several concurrent ``download_with_resume`` calls. + """One bar across several concurrent downloads, summing tracker bytes. - Each ``tracker()`` is a ``progress`` callback for one download; it reports - that file's absolute byte count and the bar shows the sum over ``total``. - The lock also serialises the bar's stderr writes, so worker threads never - interleave frames. With an unknown ``total`` (0) nothing is drawn. Call - ``done()`` once every download has finished (or failed) so a bar that - never reached 100% still ends its line before the next log message. + The lock also serialises stderr writes so workers never interleave + frames; a ``total`` of 0 draws nothing. Call ``done()`` at the end so a + bar short of 100% still ends its line. """ def __init__(self, header: str, total: int) -> None: @@ -872,11 +864,8 @@ def download_with_resume( of consuming attempts — for callers with their own fallback, like ``download_from_mirrors``. - ``progress``, when given, replaces the built-in progress bar: it is called - with the absolute number of bytes of ``dest`` obtained so far (including - a resumed prefix, and the final size once the file is verified), so a - caller running several downloads at once can draw one combined bar (see - ``BatchDownloadProgress``). + ``progress`` replaces the built-in bar: it receives the absolute bytes of + ``dest`` obtained so far (see ``BatchDownloadProgress``). Raises EsphomeError when all attempts are exhausted. """ diff --git a/esphome/platformio/library.py b/esphome/platformio/library.py index ce57cedfb4..c3c2d8edb1 100644 --- a/esphome/platformio/library.py +++ b/esphome/platformio/library.py @@ -926,10 +926,8 @@ def _prefetch_wave( ) -> None: """Best-effort parallel download of a wave's registry archives. - The walk's own ``download()`` call stays authoritative (it surfaces real - failures, with resume); bars are suppressed since parallel bars would - interleave. Duplicate URLs prefetch once so two threads never extract - into the same cache directory. + The walk's own ``download()`` stays authoritative; duplicate URLs + prefetch once so two threads never share a cache directory. """ components: list[ConvertedLibrary] = [] seen: set[str] = set() @@ -954,9 +952,8 @@ def _prefetch_wave( components.append(component) if len(components) < 2: return - # One combined bar over the batch, sized by HEAD requests. An unknown - # size would mean a silent multi-MB download; fall back to sequential - # downloads with their per-file bars instead. + # Combined bar sized by HEAD requests; unknown sizes fall back to + # sequential per-file bars rather than a silent multi-MB download sizes = _content_lengths([c.source.url for c in components]) if not all(sizes): # Announced before the sequential per-file downloads take over, so @@ -1087,8 +1084,7 @@ def convert_libraries( worklist = deque(dict.fromkeys(top_level)) while worklist: # Drain the frontier sequentially (spec resolution mutates shared - # node state), then prefetch the wave's registry archives in - # parallel; the per-component download() below stays authoritative. + # state), then prefetch the wave in parallel wave: list[tuple[str, ConvertedLibrary]] = [] while worklist: key = worklist.popleft() @@ -1117,9 +1113,8 @@ def convert_libraries( for key, component in wave: node = nodes[key] if frozenset(node.requirements) != resolved_requirements[key]: - # An earlier wave entry grew this node's requirements after - # the drain resolved it; skip parsing and walking a manifest - # the next wave will replace (its archive is already fetched) + # Requirements grew mid-wave: skip parsing a manifest the + # next wave will replace (its archive is already fetched) worklist.append(key) continue component.download(salt=salt, namespace=backend.cache_key)