Use the Google Books API key: the fallback source actually answers now
The user obtained a restricted Google Books key. Keyless requests 429 for every caller on the internet — all anonymous traffic bills to one shared Google Cloud project whose daily quota is permanently exhausted — so the documented fallback has never once answered. Because MetadataRepository.combine turns "a source failed, none found" into Unavailable, that standing failure meant every Open Library hiccup reached the user as "couldn't be reached". The app has been effectively single-sourced since it was written. Build plumbing reads GOOGLE_BOOKS_API_KEY from local.properties (gitignored), falling back to the environment and then to empty. A blank key is a supported state: a fresh clone still builds a working app that falls back to the keyless endpoint, rather than failing to build. GoogleBooksClient appends the key only when non-blank, building the URL with HttpUrl.Builder in a pure requestUrl() so it is testable without a socket. The key is scrubbed from SourceResult.Failed.reason before that string can reach the scan sheet — it is rendered to the user and is our only diagnostic channel from a real phone, and some okhttp/JDK IOExceptions embed the full request URL in their message. Defensive, not a response to an observed leak. Resolves the RATE_LIMITED decision parked in RetryPolicy's KDoc: a keyed 429 is the short per-user rate limit and gets exactly one retry, honouring Retry-After capped at 2s. A keyless 429 is still the dead daily quota and is still never retried. Verified against the live API, not only offline: both ISBNs that failed on the phone (9781883937386, 9781883937676) plus a control return HTTP 200, in the percent-encoded URL shape HttpUrl actually produces. Both books are in Google Books, so the restored fallback now covers precisely the Open Library TLS-reset failure that broke those scans. 189 unit tests (was 172), 0 failures; Paparazzi unchanged; release APK builds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J7WHnTx2Cso4VV245WDAJY
This commit is contained in:
1 parent
d6d02f788c
commit
486f6ebc48
11 files changed
+662
-37
No files matched your search
@@ -108,7 +108,7 @@ Worth saying plainly, because it bounds how much weight the numbers carry:
|
||||
|
||||
## The options
|
||||
|
||||
### A. Give Google Books an API key
|
||||
### A. Give Google Books an API key — **DONE 2026-09-11, see the end of this file**
|
||||
Free, 1,000 requests/day, no billing account required. Turns the fallback from
|
||||
"silently 429" into a working source. Roughly a dozen lines: a key in
|
||||
`local.properties` → `BuildConfig` → `&key=` on the query.
|
||||
@@ -248,7 +248,8 @@ PERMANENT standing failure, so **every** Open Library hiccup became `Unavailable
|
||||
The app has effectively been single-sourced this whole time while reporting
|
||||
failures as though two sources had been consulted.
|
||||
|
||||
**The user has deliberately deferred the API key.** Do not implement it unasked.
|
||||
**The user deferred the API key at the time; they asked for it on 2026-09-11 and it
|
||||
is now implemented and verified.** See "The key landed" at the end of this file.
|
||||
|
||||
### Open Library: 13% failure, and our own timeout was manufacturing more
|
||||
|
||||
@@ -292,3 +293,60 @@ scanning a box of books in sequence stays on the fast path after the first book.
|
||||
- The user's own 3-request sample showed 2 failures. That is consistent with 13%
|
||||
(p ~ 5%) but does not confirm it. If their phone reports "3 attempts" often,
|
||||
their network is worse than this one and the retry count deserves revisiting.
|
||||
|
||||
## The key landed — 2026-09-11
|
||||
|
||||
The user obtained a restricted Google Books API key and it is wired in
|
||||
(commit below). This closes option A, which had been the single biggest
|
||||
outstanding win in this file.
|
||||
|
||||
### Verified live, not just in tests
|
||||
|
||||
The key was tested from this machine against the real API before any code was
|
||||
written, and again afterwards in the exact URL shape the app now builds:
|
||||
|
||||
| ISBN | Result |
|
||||
|---|---|
|
||||
| 9781883937386 *Hittite Warrior* | HTTP 200, 1 item |
|
||||
| 9781883937676 *Shadow Hawk* | HTTP 200, 1 item |
|
||||
| 9780140449136 *Crime and Punishment* (control) | HTTP 200, 1 item |
|
||||
|
||||
**Both of the books that failed on the user's phone are in Google Books.** They
|
||||
were always in Open Library too — what actually failed was the TLS-stage
|
||||
connection reset documented above, not coverage. The significance is that the
|
||||
fallback would now cover exactly that failure mode: an Open Library transport
|
||||
error no longer leaves the lookup with nothing to fall back to. The app has been
|
||||
effectively single-sourced since it was written; it is now genuinely two-sourced.
|
||||
|
||||
Note the second URL test was not redundant. `HttpUrl.Builder` percent-encodes the
|
||||
colon, so the app sends `q=isbn%3A9781883937386` where every hand-run test in this
|
||||
file sent `q=isbn:9781883937386`. Offline unit tests cannot tell those apart and
|
||||
the API accepts both — but that is a fact worth having measured rather than
|
||||
assumed, because the failure mode would have been a feature that passes every
|
||||
test and returns nothing on the phone.
|
||||
|
||||
### What was built
|
||||
|
||||
- The key lives in `app/local.properties` (gitignored) as `GOOGLE_BOOKS_API_KEY`,
|
||||
read by `app/app/build.gradle.kts` into `BuildConfig.GOOGLE_BOOKS_API_KEY`,
|
||||
falling back to an environment variable of the same name and then to empty.
|
||||
**A blank key is a supported state** — a fresh clone builds a working app that
|
||||
falls back to the keyless (429ing) endpoint rather than failing to build.
|
||||
- `GoogleBooksClient` takes the key and appends it only when non-blank, with URL
|
||||
construction in a pure `requestUrl()` so it is testable with no socket.
|
||||
- **The key is scrubbed out of `SourceResult.Failed.reason`** before it can reach
|
||||
the scan sheet. That string is rendered to the user and is our only diagnostic
|
||||
channel from a real phone; some okhttp/JDK IOExceptions embed the full request
|
||||
URL in their message, so the scrub is defensive rather than a response to an
|
||||
observed leak. Do not remove it on the grounds that nothing currently leaks.
|
||||
- The `RetryPolicy` RATE_LIMITED decision parked in its KDoc is now resolved: a
|
||||
**keyed** 429 is the short per-user rate limit and gets exactly one retry,
|
||||
honouring `Retry-After` capped at 2s; a **keyless** 429 is still the dead daily
|
||||
quota and is still never retried.
|
||||
|
||||
### What this does NOT fix
|
||||
|
||||
The 17% of books with no cover art anywhere is unchanged, and so is Open
|
||||
Library's ~13% transport failure rate. What changes is that a failure of one
|
||||
source is now much more likely to be covered by the other instead of surfacing
|
||||
as `Unavailable`.
|
||||
Reference in new issue
Block a user