Keep originals on this device, by pin and by use
Fills in `image_cache`, which the previous commit's "On this device" filter read but nothing wrote. Also carries in-flight work that shared these files: the Android TLS root store, the settings page, and a regenerated traceability report. # Two populations, deliberately separate An original is kept here for one of two reasons, and conflating them produces the exact failure the feature exists to prevent. **Pinned** originals were asked for. Pinning a collection before a trip is a promise, so pinned rows are never evicted and never counted against the budget — a cap that could silently delete a pinned trip would make pinning worthless, because it could not be relied on without checking. **Passively cached** originals are a side effect of working: develop already downloads the whole file, so keeping it costs no bandwidth and saves the entire transfer next time. This population is what the budget bounds, evicted least-recently-used, because it otherwise grows until a day of culling fills a disk. Sharing one budget would let a large pin starve the passive cache, or let browsing evict a pin. They are separate. # What was built `dr_catalog::cache` owns the bookkeeping — held tier, size, last use, pinned — and writes the bytes; deciding to download stays with the caller, which is what keeps a crate with no network out of the network's business. Files are written to a temporary and renamed, so a dropped connection cannot leave a truncated file recorded as a complete original. They are named by image id, not filename: `Photos/IMG_0001.CR2` and `Trips/IMG_0001.CR2` are different photographs, and a flat cache keyed on the name would serve one for the other. `spawn_full_fetch` became read-through. A hit is a disk read; a miss stores what it downloads and enforces the budget. A cache that cannot be opened is a miss, not a failure to open the photograph. Pinning writes intent — `tier_desired` — without downloading, so the button responds immediately, and `spawn_pin_fetch` fills it in sequentially afterwards. Sequential because these are tens of megabytes each: the lanes that make the thumbnail sweep fast buy little against one connection's bandwidth and cost a great deal of memory. A pin interrupted by a lost connection resumes from where it stopped. Schema v5 adds `pinned` and `path`. `pinned` is a column rather than something inferred from `pinned_by_rule`, which is ON DELETE SET NULL and so cannot answer for an image whose rule was deleted. A v4 catalog migrates in place; existing rows default to unpinned, the safe direction. The budget and "keep opened originals" come from the settings page rather than a constant, and are applied at startup rather than only on change — a cache capped at 2 GB last session would otherwise spend this one filling to the default. Turning off keeping leaves what is already cached readable: those bytes are paid for, and refusing them would re-download images sitting right there, including pinned ones. Also removes a doubled `#[test]` introduced in the previous commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -510,6 +510,24 @@ pub fn http_client(user_agent: &str) -> Result<reqwest::Client, RemoteError> {
|
||||
install_crypto_provider();
|
||||
reqwest::Client::builder()
|
||||
.user_agent(user_agent.to_string())
|
||||
// Verify against the roots compiled into the binary rather than the
|
||||
// platform store — D7's escape hatch, and what makes TLS work on
|
||||
// Android at all.
|
||||
//
|
||||
// Without this, reqwest builds a `rustls_platform_verifier::Verifier`,
|
||||
// which reads Android's trust store over JNI and panics during the
|
||||
// handshake unless Java initialised it first. The panic lands inside a
|
||||
// tokio task, so tokio swallows it: the worker thread simply stops, the
|
||||
// channel closes, and the UI reports a failure with no error and no log
|
||||
// line to explain it.
|
||||
//
|
||||
// `tls_certs_only` is the flag reqwest branches on to skip the platform
|
||||
// verifier entirely (see its ClientBuilder TLS setup); the webpki-roots
|
||||
// feature supplies the roots it then uses. Spike S3 revisits this to
|
||||
// honour user-installed and enterprise CAs, which bundled roots cannot.
|
||||
// Empty: the flag is what matters, and the roots come from the
|
||||
// webpki-roots feature rather than from certificates passed here.
|
||||
.tls_certs_only(std::iter::empty())
|
||||
.build()
|
||||
.map_err(|e| RemoteError::Network(e.to_string()))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user