diff --git a/CHANGELOG.md b/CHANGELOG.md index 59638d3..9ceb4a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - `category_fetcher._do_ensure` no longer permanently flips `_batch_api_works` to False when a transient network error drops a tag-API request mid-call; the unprobed path now routes through `_probe_batch_api`, which distinguishes clean 200-with-zero-matches (structurally broken, flip) from timeout/HTTP-error (transient, retry next call) - Bookmark→library save and bookmark Save As now plumb the active site's `CategoryFetcher` through to the filename template, so `%artist%`/`%character%` tokens render correctly instead of silently dropping out when saving a post that wasn't previewed first - Info panel no longer silently drops tags that failed to land in a cached category — any tag from `post.tag_list` not rendered under a known category section now appears in an "Other" bucket, so partial cache coverage can't make individual tags invisible +- `BooruClient._request` retries now cover `httpx.RemoteProtocolError` and `httpx.ReadError` in addition to the existing timeout/connect/network set — an overloaded booru that drops the TCP connection mid-response no longer fails the whole search on the first try ### Refactored - `category_fetcher` batch tag-API params are now built by a shared `_build_tag_api_params` helper instead of duplicated across `fetch_via_tag_api` and `_probe_batch_api` diff --git a/booru_viewer/core/api/base.py b/booru_viewer/core/api/base.py index 679c408..e0cb332 100644 --- a/booru_viewer/core/api/base.py +++ b/booru_viewer/core/api/base.py @@ -152,9 +152,18 @@ class BooruClient(ABC): wait = 2.0 log.info(f"Retrying {url} after {resp.status_code} (wait {wait}s)") await asyncio.sleep(wait) - except (httpx.TimeoutException, httpx.ConnectError, httpx.NetworkError) as e: - # Retry on transient DNS/TCP/timeout failures. Without this, - # a single DNS hiccup or RST blows up the whole search. + except ( + httpx.TimeoutException, + httpx.ConnectError, + httpx.NetworkError, + httpx.RemoteProtocolError, + httpx.ReadError, + ) as e: + # Retry on transient DNS/TCP/timeout failures plus + # mid-response drops — RemoteProtocolError and ReadError + # are common when an overloaded booru closes the TCP + # connection between headers and body. Without them a + # single dropped response blows up the whole search. if attempt == 1: raise log.info(f"Retrying {url} after {type(e).__name__}: {e}")