G1 step 3: the Gemini fetcher (TOFU, redirects, floors, pages via the card)

GeminiService fetches on a short-lived task and hands the App a
GeminiPage: header, the body as lines in 4 KB chunks (TextBuffer: no
large block, no doubling copies), the final URL after up to 5
redirects. Certificates are pinned on first use per host and port; a
change comes back as its own outcome with both fingerprints. No fetch
starts below 55 KB free (Q86).

With a card, the body streams to /gemini/cache/page.gmi in 1 KB pieces
while the connection is open, then loads into RAM once its memory is
back (Q87); StorageService::runAndWait (moved from the Debug Console)
keeps every card access on the storage task. Without a card: RAM, with
the steady and transient floors.

Measured with IRC connected: Cosmos (31.6 KB) went from 4.6 KB to the
whole page on the card and 20 KB on screen; lowest free heap 19.5 KB
in transfer, 43 KB once loaded. `gemini get` and `gemini trust` on the
console. 14 Gemini tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-05 00:58:01 +02:00
co-authored by Claude Opus 5.5
parent babb1d114e
commit 7b8d9391a4
12 changed files with 552 additions and 130 deletions
+3
View File
@@ -42,6 +42,9 @@ class StorageService : public Service {
// Runs `job` on the storage task, where card access is safe (e.g. reading an Update File).
void runJob(std::function<void()> job);
// The same, and waits for it (from another task, never the storage task itself). False if it
// never started within 5 s: no card mounted, and then it never will run.
bool runAndWait(std::function<void()> job);
// Erases the whole card: one partition spanning the card, formatted FAT32.
bool requestFormat();