diff --git a/src/providers/hentaihaven.rs b/src/providers/hentaihaven.rs index 00242b4..f341181 100644 --- a/src/providers/hentaihaven.rs +++ b/src/providers/hentaihaven.rs @@ -86,6 +86,17 @@ impl HentaihavenProvider { } } + fn has_playable_formats(item: &VideoItem) -> bool { + item.formats + .as_ref() + .is_some_and(|formats| formats.iter().any(|format| !format.url.trim().is_empty())) + } + + fn decode_cached_video(cached: &str) -> Option { + let item = VideoItem::from(cached.to_string()).ok()?; + Self::has_playable_formats(&item).then_some(item) + } + async fn get( &self, cache: VideoCache, @@ -353,12 +364,13 @@ impl HentaihavenProvider { drop(conn); match db_result { Ok(Some(video)) => { - let video_item = VideoItem::from(video); - match video_item { - Ok(item) => return Ok(item), - Err(e) => { - eprint!("Failed to convert video from DB result: {}\n", e); - } + if let Some(item) = Self::decode_cached_video(&video) { + return Ok(item); + } + + eprint!("Ignoring stale hentaihaven DB cache entry without playable formats\n"); + if let Ok(mut conn) = pool.get() { + let _ = db::delete_video(&mut conn, video_url.clone()); } } Ok(None) => { @@ -530,6 +542,49 @@ impl HentaihavenProvider { } } +#[cfg(test)] +mod tests { + use super::HentaihavenProvider; + use crate::videos::{VideoFormat, VideoItem}; + + #[test] + fn accepts_cached_items_with_playable_formats() { + let cached = serde_json::to_string( + &VideoItem::new( + "id".to_string(), + "title".to_string(), + "https://hentaihaven.xxx/video/test/".to_string(), + "hentaihaven".to_string(), + "https://example.com/thumb.jpg".to_string(), + 0, + ) + .formats(vec![VideoFormat::new( + "https://cdn.example/master.m3u8".to_string(), + "1080p".to_string(), + "m3u8".to_string(), + )]), + ) + .expect("serializes"); + + assert!(HentaihavenProvider::decode_cached_video(&cached).is_some()); + } + + #[test] + fn rejects_cached_items_without_formats() { + let cached = serde_json::to_string(&VideoItem::new( + "id".to_string(), + "title".to_string(), + "https://hentaihaven.xxx/video/test/".to_string(), + "hentaihaven".to_string(), + "https://example.com/thumb.jpg".to_string(), + 0, + )) + .expect("serializes"); + + assert!(HentaihavenProvider::decode_cached_video(&cached).is_none()); + } +} + #[async_trait] impl Provider for HentaihavenProvider { async fn get_videos(