From bfaff6b4e783d90de0f2531a0753a9c42cd2baf5 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 22 Jan 2025 18:41:38 +0100 Subject: [PATCH 1/3] feat: add mark season as watched for videos --- src/models/meta_details.rs | 39 +++++++++++++++++++++++++++++ src/models/player.rs | 41 +++++++++++++++++++++++++++++++ src/runtime/msg/action.rs | 4 +++ src/types/library/library_item.rs | 28 +++++++++++++++++++++ 4 files changed, 112 insertions(+) diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs index 76b68e341..9ce2f6953 100644 --- a/src/models/meta_details.rs +++ b/src/models/meta_details.rs @@ -1,5 +1,6 @@ use std::{borrow::Cow, marker::PhantomData}; +use itertools::Itertools; use serde::{Deserialize, Serialize}; use stremio_watched_bitfield::WatchedBitField; @@ -131,6 +132,44 @@ impl UpdateWithCtx for MetaDetails { } _ => Effects::none().unchanged(), }, + Msg::Action(Action::MetaDetails(ActionMetaDetails::MarkSeasonAsWatched( + season, + is_watched, + ))) => match (&self.library_item, &self.watched) { + (Some(library_item), Some(watched)) => { + // Find videos of given season from the first ready meta item loadable + let videos = self + .meta_items + .iter() + .find(|meta_item| matches!(&meta_item.content, Some(Loadable::Ready(_)))) + .and_then(|meta_item| meta_item.content.as_ref()) + .and_then(|meta_item| meta_item.ready()) + .map(|meta_item| { + meta_item + .videos + .iter() + .filter(|video| { + video + .series_info + .as_ref() + .is_some_and(|series_info| series_info.season == *season) + }) + .collect_vec() + }); + + match videos { + Some(videos) => { + let mut library_item = library_item.to_owned(); + library_item.mark_videos_as_watched::(watched, videos, *is_watched); + + Effects::msg(Msg::Internal(Internal::UpdateLibraryItem(library_item))) + .unchanged() + } + None => Effects::none().unchanged(), + } + } + _ => Effects::none().unchanged(), + }, Msg::Internal(Internal::ResourceRequestResult(request, result)) if request.path.resource == META_RESOURCE_NAME => { diff --git a/src/models/player.rs b/src/models/player.rs index e58a792c2..718360fcf 100644 --- a/src/models/player.rs +++ b/src/models/player.rs @@ -675,6 +675,47 @@ impl UpdateWithCtx for Player { _ => Effects::none().unchanged(), } } + Msg::Action(Action::Player(ActionPlayer::MarkSeasonAsWatched(season, is_watched))) => { + match (&self.library_item, &self.watched) { + (Some(library_item), Some(watched)) => { + // Find videos of given season from the first ready meta item loadable + let videos = self + .meta_item + .as_ref() + .and_then(|meta_item| meta_item.content.as_ref()) + .and_then(|meta_item| meta_item.ready()) + .map(|meta_item| { + meta_item + .videos + .iter() + .filter(|video| { + video.series_info.as_ref().is_some_and(|series_info| { + series_info.season == *season + }) + }) + .collect_vec() + }); + + match videos { + Some(videos) => { + let mut library_item = library_item.to_owned(); + library_item.mark_videos_as_watched::( + watched, + videos, + *is_watched, + ); + + Effects::msg(Msg::Internal(Internal::UpdateLibraryItem( + library_item, + ))) + .unchanged() + } + None => Effects::none().unchanged(), + } + } + _ => Effects::none().unchanged(), + } + } Msg::Internal(Internal::LibraryChanged(_)) => { let library_item_effects = library_item_update::( &mut self.library_item, diff --git a/src/runtime/msg/action.rs b/src/runtime/msg/action.rs index fd74e54c5..1ed66fb1a 100644 --- a/src/runtime/msg/action.rs +++ b/src/runtime/msg/action.rs @@ -117,6 +117,8 @@ pub enum ActionMetaDetails { /// /// [`LibraryItem`]: crate::types::library::LibraryItem MarkVideoAsWatched(Video, bool), + /// Mark all videos from given season as watched + MarkSeasonAsWatched(u32, bool), } #[derive(Clone, Deserialize, Debug)] @@ -199,6 +201,8 @@ pub enum ActionPlayer { /// /// [`LibraryItem`]: crate::types::library::LibraryItem MarkVideoAsWatched(Video, bool), + /// Mark all videos from given season as watched + MarkSeasonAsWatched(u32, bool), } #[derive(Clone, Deserialize, Debug)] diff --git a/src/types/library/library_item.rs b/src/types/library/library_item.rs index 3d1143bc6..9b2429400 100644 --- a/src/types/library/library_item.rs +++ b/src/types/library/library_item.rs @@ -128,6 +128,34 @@ impl LibraryItem { }; } } + + pub fn mark_videos_as_watched( + &mut self, + watched: &WatchedBitField, + videos: Vec<&Video>, + is_watched: bool, + ) { + let mut watched = watched.to_owned(); + + for video in &videos { + watched.set_video(&video.id, is_watched); + } + + self.state.watched = Some(watched.into()); + + if is_watched { + self.state.last_watched = match ( + &self.state.last_watched, + videos.last().and_then(|last_video| last_video.released), + ) { + (Some(last_watched), Some(released)) if last_watched < &released => { + Some(released.to_owned()) + } + (None, released) => released.to_owned(), + (last_watched, _) => last_watched.to_owned(), + }; + } + } } impl From<(&MetaItemPreview, PhantomData)> for LibraryItem { From 0b2e5c2cc79648f160663697700617f65a2fd608 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 27 Jan 2025 19:06:39 +0100 Subject: [PATCH 2/3] docs(player): update MarkSeasonAsWatched comment --- src/models/player.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/player.rs b/src/models/player.rs index 718360fcf..22beb42d8 100644 --- a/src/models/player.rs +++ b/src/models/player.rs @@ -678,7 +678,7 @@ impl UpdateWithCtx for Player { Msg::Action(Action::Player(ActionPlayer::MarkSeasonAsWatched(season, is_watched))) => { match (&self.library_item, &self.watched) { (Some(library_item), Some(watched)) => { - // Find videos of given season from the first ready meta item loadable + // Find videos of given season from the meta item loadable let videos = self .meta_item .as_ref() From 59da7623a6ca50c09b23c88571c9c65404b11a08 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 27 Jan 2025 19:13:51 +0100 Subject: [PATCH 3/3] refactor: move videos by season logic to a function --- src/models/meta_details.rs | 14 +------------- src/models/player.rs | 12 +----------- src/types/resource/meta_item.rs | 13 +++++++++++++ 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs index 9ce2f6953..11de99f4e 100644 --- a/src/models/meta_details.rs +++ b/src/models/meta_details.rs @@ -1,6 +1,5 @@ use std::{borrow::Cow, marker::PhantomData}; -use itertools::Itertools; use serde::{Deserialize, Serialize}; use stremio_watched_bitfield::WatchedBitField; @@ -144,18 +143,7 @@ impl UpdateWithCtx for MetaDetails { .find(|meta_item| matches!(&meta_item.content, Some(Loadable::Ready(_)))) .and_then(|meta_item| meta_item.content.as_ref()) .and_then(|meta_item| meta_item.ready()) - .map(|meta_item| { - meta_item - .videos - .iter() - .filter(|video| { - video - .series_info - .as_ref() - .is_some_and(|series_info| series_info.season == *season) - }) - .collect_vec() - }); + .map(|meta_item| meta_item.videos_by_season(*season)); match videos { Some(videos) => { diff --git a/src/models/player.rs b/src/models/player.rs index 22beb42d8..e735604dd 100644 --- a/src/models/player.rs +++ b/src/models/player.rs @@ -684,17 +684,7 @@ impl UpdateWithCtx for Player { .as_ref() .and_then(|meta_item| meta_item.content.as_ref()) .and_then(|meta_item| meta_item.ready()) - .map(|meta_item| { - meta_item - .videos - .iter() - .filter(|video| { - video.series_info.as_ref().is_some_and(|series_info| { - series_info.season == *season - }) - }) - .collect_vec() - }); + .map(|meta_item| meta_item.videos_by_season(*season)); match videos { Some(videos) => { diff --git a/src/types/resource/meta_item.rs b/src/types/resource/meta_item.rs index 1e52f4dcf..be928b7c2 100644 --- a/src/types/resource/meta_item.rs +++ b/src/types/resource/meta_item.rs @@ -265,6 +265,19 @@ impl MetaItem { Either::Right(self.videos.iter().rev()) } } + + /// Returns a vector of videos for a given season + pub fn videos_by_season(&self, season: u32) -> Vec<&Video> { + self.videos + .iter() + .filter(|video| { + video + .series_info + .as_ref() + .is_some_and(|series_info| series_info.season == season) + }) + .collect_vec() + } } #[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]