vc=88: deferred-hygiene sweep (audit #2 leftovers, no behavior change)
M-2: route every SharedPreferences write (Settings/History/Subs/Resume) through one PrefsWriter — a per-store single-thread dispatcher — so the on-disk apply() order matches the in-memory CAS order. Previously a Main-thread toggle and an IO-thread import could land apply() out of order, and ResumePositions detached ordering entirely via a fresh globalScope.launch per write; a stale value could then win the next cold-start load. Each write reads the live StateFlow so disk converges to the latest in-memory state regardless of enqueue order. L-14: Settings storage-usage sampling (File.length() x4 + Coil diskCache.size) moved off the composition/Main thread into a LaunchedEffect on Dispatchers.IO. L-2 / L-4..L-8 / L-15 / L-16: dead code + stale comments from the vc=85 SB/RYD to Rust migration. Http.kt trimmed to STRAW_USER_AGENT; reconciled the network_security_config / feed.rs / SubscriptionFeedViewModel / net.rs / CI comments with reality; recencyScore overflow-guarded; ci/Dockerfile now pre-installs build-tools 36 (AGP 9.2.1's actual floor, was auto-fetched). Verified: headless compileDebugKotlin green on the straw-build image.
This commit is contained in:
parent
1fe6c12f1d
commit
457166e3b0
14 changed files with 256 additions and 183 deletions
|
|
@ -105,12 +105,17 @@ pub async fn channel_feed_rss(
|
|||
crate::runtime::ensure_initialized();
|
||||
log::info!("strawcore::channel_feed_rss url_len={}", channel_url.len());
|
||||
let client = rss_client()?;
|
||||
// Propagate the real failure (network / HTTP / parse) instead of
|
||||
// collapsing it to an empty Vec. The single-channel caller (the subs
|
||||
// feed) needs to tell "this channel posted nothing" apart from "this
|
||||
// fetch broke" — the prior `.unwrap_or_default()` made the `Result`
|
||||
// a lie. (subscription_feed keeps its own per-channel unwrap_or_default
|
||||
// below — fan-out tolerance is correct there, not here.)
|
||||
// Propagate the real failure (network / HTTP / parse) as Err rather than
|
||||
// collapsing it to an empty Vec. The current Android caller flattens
|
||||
// Err -> emptyList() and only overwrites its per-channel cache on a
|
||||
// NON-empty result, so a transient fetch error leaves the prior cache
|
||||
// intact instead of blanking the channel — which is exactly why the old
|
||||
// `.unwrap_or_default()` here was wrong (it turned a broken fetch into an
|
||||
// authoritative "no videos"). Note an unsupported URL (`extract_channel_id`
|
||||
// -> None) and a genuinely empty feed both still return Ok(vec![]); the
|
||||
// Err vs Ok distinction only separates *broken* from *empty*.
|
||||
// subscription_feed keeps its own per-channel unwrap_or_default below —
|
||||
// fan-out tolerance is correct there, not here.
|
||||
fetch_channel_rss(client, &channel_url).await
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@ fn client() -> Option<&'static Client> {
|
|||
/// exceeds `cap`. Shared with the RSS feed path (`feed.rs`). Per-chunk
|
||||
/// guard first (HTTP allows multi-GiB chunks; hyper may have already
|
||||
/// allocated one before we see it), then the running total.
|
||||
///
|
||||
/// The cap bounds gzip-DECOMPRESSED bytes (reqwest is built with `gzip`),
|
||||
/// not wire bytes — same as the old OkHttp `cappedString`, and fine for the
|
||||
/// few-hundred-byte-to-low-KB RYD / SponsorBlock / RSS payloads. A hostile
|
||||
/// host could force up-to-`cap` decompression, but never enough to OOM.
|
||||
pub(crate) async fn read_capped_body(resp: reqwest::Response, cap: usize) -> Option<String> {
|
||||
use futures::StreamExt;
|
||||
let mut total = 0usize;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue