mirror of
https://github.com/janeczku/calibre-web
synced 2026-10-19 14:22:26 +00:00
A failed metadata search is reported to the user as "No Result(s) found! Please try another keyword." — the same words as a genuine miss, so the user goes looking for a keyword problem that is not there. search() swallows every exception and returns [], which the route cannot tell from an empty result. Three ways it happens: 1. No API key, which is the default. An unkeyed Books call is billed to a shared anonymous Google project whose daily quota is permanently spent: 429 RESOURCE_EXHAUSTED, "Quota exceeded ... for consumer project_number:<redacted>". The same project number comes back from two unrelated networks, so it is shared, not per-IP. Every stock install with no key therefore gets nothing, and is told its keyword is wrong. 2. Transient 5xx with a valid key: 2 of 12 single-shot keyed requests answered 503. There is no retry anywhere in cps/. 3. No request timeout, so a stalled connection holds the search thread until the client gives up. /metadata/search waits on every provider. Changes: - google.py retries 500/502/503/504 (3 retries, 0.3 backoff) and passes a (5, 15) timeout. 429 is deliberately not retried: a spent daily quota does not clear, so it must fail fast. - google.py returns None on failure and [] on an empty result. The signature is already Optional[List[MetaRecord]] and callers already handle None. - search_metadata.py answers 502 when every provider it asked failed. get_meta.js already tells msg.search_error from msg.no_result; it had no way to know which had happened. "Search error!" is an existing string, translated in every locale — no new text, no JS change. - search_metadata.py no longer lets one provider's exception fail the whole request, which used to discard every other provider's results. - The API key is read per search instead of at import, so saving one in the admin page takes effect without a restart. allowed_methods is left at the urllib3 default, which already covers GET; naming it would need urllib3 >= 1.26 and requirements.txt allows 1.22. The Session is built per call because requests.Session is not thread-safe and the providers are searched in a thread pool. Not included, happy to add: cps/admin.py still sets reboot_required for config_googlebooks_api_key, now unnecessary; and the other five providers still return [] on failure.