Public Access
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:
@@ -5,6 +5,7 @@
|
||||
#include "gemini_response.h"
|
||||
#include "gemini_url.h"
|
||||
#include "gemtext.h"
|
||||
#include "text_buffer.h"
|
||||
|
||||
using namespace roro::gemini;
|
||||
|
||||
@@ -174,6 +175,34 @@ void test_saved_page_paths() {
|
||||
savedPath("gemini://example.org:1966/a?q:x#frag").c_str());
|
||||
}
|
||||
|
||||
void test_text_buffer_keeps_lines_across_any_feed() {
|
||||
TextBuffer b;
|
||||
std::string doc = "# Title\r\nline two\n\nlast without end";
|
||||
for (size_t i = 0; i < doc.size(); i += 3) b.append(doc.data() + i, std::min<size_t>(3, doc.size() - i));
|
||||
b.finish();
|
||||
TEST_ASSERT_EQUAL(4, static_cast<int>(b.lineCount()));
|
||||
TEST_ASSERT_EQUAL_STRING("# Title", b.line(0).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("line two", b.line(1).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("", b.line(2).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("last without end", b.line(3).c_str());
|
||||
TEST_ASSERT_EQUAL(doc.size(), b.bytes());
|
||||
}
|
||||
|
||||
void test_text_buffer_spreads_over_chunks_without_splitting_lines() {
|
||||
TextBuffer b;
|
||||
std::string line(1000, 'x');
|
||||
for (int i = 0; i < 20; i++) {
|
||||
std::string l = std::to_string(i) + line + "\n";
|
||||
b.append(l.data(), l.size());
|
||||
}
|
||||
std::string big(6000, 'y'); // longer than a chunk
|
||||
big += "\n";
|
||||
b.append(big.data(), big.size());
|
||||
TEST_ASSERT_EQUAL(21, static_cast<int>(b.lineCount()));
|
||||
for (int i = 0; i < 20; i++) TEST_ASSERT_EQUAL_STRING((std::to_string(i) + line).c_str(), b.line(i).c_str());
|
||||
TEST_ASSERT_EQUAL(6000, static_cast<int>(b.line(20).size()));
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_rfc3986_normal_examples);
|
||||
@@ -188,5 +217,7 @@ int main() {
|
||||
RUN_TEST(test_gemtext_handles_crlf_and_a_missing_last_newline);
|
||||
RUN_TEST(test_display_text_keeps_latin1_and_replaces_the_rest);
|
||||
RUN_TEST(test_saved_page_paths);
|
||||
RUN_TEST(test_text_buffer_keeps_lines_across_any_feed);
|
||||
RUN_TEST(test_text_buffer_spreads_over_chunks_without_splitting_lines);
|
||||
return UNITY_END();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user