From 2f11bdd40afea961aa81e444ac148f86934691da Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Sun, 11 Oct 2026 19:49:11 +0200 Subject: [PATCH] fix: discard malformed video entries without failing pages --- Cargo.toml | 2 +- README.md | 2 + src/main.rs | 2 + src/youtube.rs | 141 +++++++++++++++++++++++++++++++++++-------------- 4 files changed, 106 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0c05c5a..64d95ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ image = { version = "0.25.10", default-features = false, features = ["jpeg"] } ratatui = { version = "0.30.2", default-features = false, features = ["crossterm", "unstable-rendered-line-info"] } ratatui-image = { version = "11.1.0", default-features = false, features = ["crossterm"] } serde = { version = "1.0.229", features = ["derive"] } -serde_json = "1.0.151" +serde_json = { version = "1.0.151", features = ["raw_value"] } toml_edit = "0.25.17" time = "0.3.55" diff --git a/README.md b/README.md index 3f61348..16dadee 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,8 @@ Using the same binding for overview and description preserves the default cycle. Search loads 20 results per page; Feed and category views request 20 uploads per channel per page. Individual channel views request only that channel's uploads, using the same pagination. Moving near the loaded end in normal mode fetches more asynchronously, preserving selection and existing rows. Insert mode and open popups pause automatic prefetch. Deep channel archives can get slower because yt-dlp offsets traverse earlier pages again. +Malformed individual entries—including invalid video IDs, missing required fields, and wrong field types—are discarded without losing valid videos from the same page. Skipped entries still count toward pagination, so a full page of bad or unavailable entries does not hide later results. Invalid JSON, malformed playlist-level metadata, and extractor failures still fail the page. + Feed views merge newest-first using available upload dates. Older uploads wait while newer channel pages catch up; undated uploads appear when the feed is exhausted. Publication labels use `YYYY-MM-DD`; `~` marks an estimated UTC date, and `Unknown` means unavailable metadata. Pane titles show animated spinners while content loads. Errors retain loaded rows and stop automatic retries; `r` refreshes the current view. diff --git a/src/main.rs b/src/main.rs index e696de7..dfa9a32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -73,6 +73,8 @@ //! Feed includes saved `[channels]` entries, even untagged, plus direct tag entries, without duplicates. //! Untagging is not unsubscribing: remove mappings and tag references from the file to unsubscribe. //! Search pages contain 20 results; Feed/tag pages request 20 uploads per channel's /videos tab. +//! Invalid individual video entries are discarded; raw entry counts still determine pagination. +//! Invalid JSON, playlist-level metadata, and extractor failures remain page errors. //! In normal mode, approaching the loaded end fetches the next page without replacing rows/selection. //! Loading result/preview panes animate a spinner after their title, including additional pages. //! Info spinners cover image download/decode/resize and description fetching; errors stop them. diff --git a/src/youtube.rs b/src/youtube.rs index 6580e5d..8831d47 100644 --- a/src/youtube.rs +++ b/src/youtube.rs @@ -30,8 +30,10 @@ pub struct Video { } #[derive(Deserialize)] -struct SearchResults { - entries: Vec>, +struct SearchResults<'a> { + // Borrow raw rows so one schema error cannot abort siblings or copy unused metadata. + #[serde(borrow)] + entries: Vec<&'a serde_json::value::RawValue>, channel: Option, channel_id: Option, uploader_id: Option, @@ -41,10 +43,15 @@ pub fn search_results(output: &str) -> Result { let results: SearchResults = serde_json::from_str(output) .map_err(|error| format!("Invalid search response: {error}"))?; let has_more = results.entries.len() >= PAGE_SIZE; - let mut videos: Vec