diff --git a/src/constants.rs b/src/constants.rs index 5e6ac0970..ed3c49a0a 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -9,6 +9,7 @@ pub const PROFILE_STORAGE_KEY: &str = "profile"; pub const LIBRARY_STORAGE_KEY: &str = "library"; pub const LIBRARY_RECENT_STORAGE_KEY: &str = "library_recent"; pub const STREAMS_STORAGE_KEY: &str = "streams"; +pub const SEARCH_HISTORY_STORAGE_KEY: &str = "search_history"; pub const NOTIFICATIONS_STORAGE_KEY: &str = "notifications"; pub const LIBRARY_COLLECTION_NAME: &str = "libraryItem"; pub const SEARCH_EXTRA_NAME: &str = "search"; diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index 8fdcbab93..bf40a972e 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -49,7 +49,7 @@ pub struct ExternalPlayerLink { pub file_name: Option, } -/// Using &Option is not encouraged, use `.as_ref()` to get an `Option<&Url>` instead! +/// Using `&Option` is not encouraged, use `.as_ref()` to get an `Option<&Url>` instead! impl From<(&Stream, &Option, &Settings)> for ExternalPlayerLink { fn from((stream, streaming_server_url, settings): (&Stream, &Option, &Settings)) -> Self { Self::from((stream, streaming_server_url.as_ref(), settings)) @@ -64,6 +64,8 @@ impl From<(&Stream, Option<&Url>, &Settings)> for ExternalPlayerLink { /// /// [`StreamingServer::base_url`]: crate::models::streaming_server::StreamingServer::base_url fn from((stream, streaming_server_url, settings): (&Stream, Option<&Url>, &Settings)) -> Self { + // Use streaming_server_url from settings if streaming_server is reachable + let streaming_server_url = streaming_server_url.map(|_| &settings.streaming_server_url); let http_regex = Regex::new(r"https?://").unwrap(); let download = stream.download_url(); let streaming = stream.streaming_url(streaming_server_url); @@ -110,6 +112,14 @@ impl From<(&Stream, Option<&Url>, &Settings)> for ExternalPlayerLink { ios: Some(format!("infuse://x-callback-url/play?url={url}")), ..Default::default() }), + "iina" => Some(OpenPlayerLink { + macos: Some(format!("iina://weblink?url={url}")), + ..Default::default() + }), + "mpv" => Some(OpenPlayerLink { + macos: Some(format!("mpv://{url}")), + ..Default::default() + }), _ => None, }, None => None, diff --git a/src/models/catalogs_with_extra.rs b/src/models/catalogs_with_extra.rs index 17fc5750a..d54042848 100644 --- a/src/models/catalogs_with_extra.rs +++ b/src/models/catalogs_with_extra.rs @@ -36,7 +36,22 @@ impl UpdateWithCtx for CatalogsWithExtra { let selected_effects = selected_update(&mut self.selected, selected); let catalogs_effects = catalogs_update::(&mut self.catalogs, &self.selected, None, &ctx.profile); - selected_effects.join(catalogs_effects) + let search_effects = match &self.selected { + Some(Selected { extra, .. }) => match extra + .iter() + .find(|ExtraValue { name, .. }| name == "search") + { + Some(ExtraValue { value, .. }) => { + Effects::msg(Msg::Internal(Internal::CatalogsWithExtraSearch { + query: value.to_owned(), + })) + .unchanged() + } + None => Effects::none().unchanged(), + }, + None => Effects::none().unchanged(), + }; + selected_effects.join(catalogs_effects).join(search_effects) } Msg::Action(Action::Unload) => { let selected_effects = eq_update(&mut self.selected, None); diff --git a/src/models/ctx/ctx.rs b/src/models/ctx/ctx.rs index 3c67176c2..6aa37782b 100644 --- a/src/models/ctx/ctx.rs +++ b/src/models/ctx/ctx.rs @@ -1,8 +1,8 @@ use crate::constants::LIBRARY_COLLECTION_NAME; use crate::models::common::{DescriptorLoadable, ResourceLoadable}; use crate::models::ctx::{ - update_library, update_notifications, update_profile, update_streams, update_trakt_addon, - CtxError, + update_library, update_notifications, update_profile, update_search_history, update_streams, + update_trakt_addon, CtxError, }; use crate::runtime::msg::{Action, ActionCtx, Event, Internal, Msg}; use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt, Update}; @@ -14,6 +14,7 @@ use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Auth, AuthKey, Profile}; use crate::types::resource::MetaItem; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; #[cfg(test)] @@ -44,6 +45,8 @@ pub struct Ctx { #[serde(skip)] pub streams: StreamsBucket, #[serde(skip)] + pub search_history: SearchHistoryBucket, + #[serde(skip)] #[cfg_attr(test, derivative(Default(value = "CtxStatus::Ready")))] pub status: CtxStatus, #[serde(skip)] @@ -59,11 +62,13 @@ impl Ctx { library: LibraryBucket, streams: StreamsBucket, notifications: NotificationsBucket, + search_history: SearchHistoryBucket, ) -> Self { Self { profile, library, streams, + search_history, notifications, trakt_addon: None, notification_catalogs: vec![], @@ -90,6 +95,8 @@ impl Update for Ctx { let library_effects = update_library::(&mut self.library, &self.profile, &self.status, msg); let streams_effects = update_streams::(&mut self.streams, &self.status, msg); + let search_history_effects = + update_search_history::(&mut self.search_history, &self.status, msg); let trakt_addon_effects = update_trakt_addon::( &mut self.trakt_addon, &self.profile, @@ -111,6 +118,7 @@ impl Update for Ctx { .join(profile_effects) .join(library_effects) .join(streams_effects) + .join(search_history_effects) .join(trakt_addon_effects) .join(notifications_effects) } @@ -134,6 +142,8 @@ impl Update for Ctx { msg, ); let streams_effects = update_streams::(&mut self.streams, &self.status, msg); + let search_history_effects = + update_search_history::(&mut self.search_history, &self.status, msg); let ctx_effects = match &self.status { CtxStatus::Loading(loading_auth_request) if loading_auth_request == auth_request => @@ -160,6 +170,7 @@ impl Update for Ctx { .join(streams_effects) .join(trakt_addon_effects) .join(notifications_effects) + .join(search_history_effects) .join(ctx_effects) } _ => { @@ -182,11 +193,14 @@ impl Update for Ctx { &self.status, msg, ); + let search_history_effects = + update_search_history::(&mut self.search_history, &self.status, msg); profile_effects .join(library_effects) .join(streams_effects) .join(trakt_addon_effects) .join(notifications_effects) + .join(search_history_effects) } } } diff --git a/src/models/ctx/mod.rs b/src/models/ctx/mod.rs index 5f5c771bc..b6b9ee85e 100644 --- a/src/models/ctx/mod.rs +++ b/src/models/ctx/mod.rs @@ -10,6 +10,9 @@ use update_profile::*; mod update_streams; use update_streams::*; +mod update_search_history; +use update_search_history::*; + mod update_trakt_addon; use update_trakt_addon::*; diff --git a/src/models/ctx/update_search_history.rs b/src/models/ctx/update_search_history.rs new file mode 100644 index 000000000..f6cb746ce --- /dev/null +++ b/src/models/ctx/update_search_history.rs @@ -0,0 +1,63 @@ +use enclose::enclose; +use futures::FutureExt; + +use crate::constants::SEARCH_HISTORY_STORAGE_KEY; +use crate::models::ctx::{CtxError, CtxStatus}; +use crate::runtime::msg::{Action, ActionCtx, Event, Internal}; +use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt}; +use crate::{runtime::msg::Msg, types::search_history::SearchHistoryBucket}; + +pub fn update_search_history( + search_history: &mut SearchHistoryBucket, + status: &CtxStatus, + msg: &Msg, +) -> Effects { + match msg { + Msg::Action(Action::Ctx(ActionCtx::Logout)) | Msg::Internal(Internal::Logout) => { + let next_search_history = SearchHistoryBucket::default(); + *search_history = next_search_history; + Effects::msg(Msg::Internal(Internal::SearchHistoryChanged)) + } + Msg::Action(Action::Ctx(ActionCtx::ClearSearchHistory)) => { + search_history.items.clear(); + Effects::msg(Msg::Internal(Internal::SearchHistoryChanged)) + } + Msg::Internal(Internal::CatalogsWithExtraSearch { query }) => { + search_history.items.insert(query.to_owned(), E::now()); + Effects::msg(Msg::Internal(Internal::SearchHistoryChanged)) + } + Msg::Internal(Internal::CtxAuthResult(auth_request, result)) => match (status, result) { + (CtxStatus::Loading(loading_auth_request), Ok((auth, ..))) + if loading_auth_request == auth_request => + { + let next_search_history = SearchHistoryBucket::new(Some(auth.user.id.to_owned())); + *search_history = next_search_history; + Effects::msg(Msg::Internal(Internal::SearchHistoryChanged)) + } + _ => Effects::none().unchanged(), + }, + Msg::Internal(Internal::SearchHistoryChanged) => { + Effects::one(push_search_history_to_storage::(search_history)).unchanged() + } + _ => Effects::none().unchanged(), + } +} + +fn push_search_history_to_storage( + search_history: &SearchHistoryBucket, +) -> Effect { + EffectFuture::Sequential( + E::set_storage(SEARCH_HISTORY_STORAGE_KEY, Some(&search_history)) + .map( + enclose!((search_history.uid => uid) move |result| match result { + Ok(_) => Msg::Event(Event::SearchHistoryPushedToStorage { uid }), + Err(error) => Msg::Event(Event::Error { + error: CtxError::from(error), + source: Box::new(Event::SearchHistoryPushedToStorage { uid }), + }) + }), + ) + .boxed_env(), + ) + .into() +} diff --git a/src/runtime/env.rs b/src/runtime/env.rs index c7f1a0463..012d6e72f 100644 --- a/src/runtime/env.rs +++ b/src/runtime/env.rs @@ -1,7 +1,7 @@ use crate::addon_transport::{AddonHTTPTransport, AddonTransport, UnsupportedTransport}; use crate::constants::{ LIBRARY_RECENT_STORAGE_KEY, LIBRARY_STORAGE_KEY, PROFILE_STORAGE_KEY, SCHEMA_VERSION, - SCHEMA_VERSION_STORAGE_KEY, STREAMS_STORAGE_KEY, + SCHEMA_VERSION_STORAGE_KEY, SEARCH_HISTORY_STORAGE_KEY, STREAMS_STORAGE_KEY, }; use crate::models::ctx::Ctx; use crate::models::streaming_server::StreamingServer; @@ -498,6 +498,12 @@ fn migrate_storage_schema_to_v9() -> TryEnvFuture<()> { } fn migrate_storage_schema_to_v10() -> TryEnvFuture<()> { + E::set_storage::<()>(SEARCH_HISTORY_STORAGE_KEY, None) + .and_then(|_| E::set_storage(SCHEMA_VERSION_STORAGE_KEY, Some(&10))) + .boxed_env() +} + +fn migrate_storage_schema_to_v11() -> TryEnvFuture<()> { E::get_storage::(PROFILE_STORAGE_KEY) .and_then(|mut profile| { match profile @@ -572,6 +578,18 @@ mod test { ); } + fn assert_storage_shema_version(schema_v: u32) { + let storage = STORAGE.read().expect("Should lock"); + + assert_eq!( + &schema_v.to_string(), + storage + .get(SCHEMA_VERSION_STORAGE_KEY) + .expect("Should have the schema set"), + "Scheme version should be {schema_v}" + ); + } + #[tokio::test] async fn test_migration_to_latest_version() { { @@ -898,6 +916,18 @@ mod test { #[tokio::test] async fn test_migration_from_9_to_10() { + let _test_env_guard = TestEnv::reset().expect("Should lock TestEnv"); + + migrate_storage_schema_to_v10::() + .await + .expect("Should migrate"); + + { + assert_storage_shema_version(10); + } + } + + async fn test_migration_from_10_to_11() { { let _test_env_guard = TestEnv::reset().expect("Should lock TestEnv"); let profile_before = json!({ diff --git a/src/runtime/msg/action.rs b/src/runtime/msg/action.rs index 3377c674b..3087dad53 100644 --- a/src/runtime/msg/action.rs +++ b/src/runtime/msg/action.rs @@ -21,7 +21,7 @@ use crate::{ types::{ addon::Descriptor, api::AuthRequest, - library::LibraryItemId, + library::{LibraryItemId, LibraryItem}, profile::Settings as ProfileSettings, resource::{MetaItemId, MetaItemPreview, Video}, }, @@ -45,6 +45,7 @@ pub enum ActionCtx { ToggleLibraryItemNotifications(LibraryItemId, bool), /// Dismiss all Notification for a given [`MetaItemId`]. DismissNotificationItem(MetaItemId), + ClearSearchHistory, PushUserToAPI, PullUserFromAPI, PushAddonsToAPI, @@ -85,10 +86,14 @@ pub enum ActionMetaDetails { /// Marks the [`LibraryItem`] as watched. /// /// Applicable when you have single-video (e.g. a movie) and multi-video (e.g. a movie series) item. + /// + /// [`LibraryItem`]: crate::types::library::LibraryItem MarkAsWatched(bool), /// Marks the given [`Video`] of the [`LibraryItem`] as watched. /// /// Applicable only when you have a multi-video (e.g. movie series) item. + /// + /// [`LibraryItem`]: crate::types::library::LibraryItem MarkVideoAsWatched(Video, bool), } diff --git a/src/runtime/msg/event.rs b/src/runtime/msg/event.rs index f28ed44c1..c7712afcf 100644 --- a/src/runtime/msg/event.rs +++ b/src/runtime/msg/event.rs @@ -40,6 +40,9 @@ pub enum Event { StreamsPushedToStorage { uid: UID, }, + SearchHistoryPushedToStorage { + uid: UID, + }, NotificationsPushedToStorage { ids: Vec, }, diff --git a/src/runtime/msg/internal.rs b/src/runtime/msg/internal.rs index 777453673..6477c24fb 100644 --- a/src/runtime/msg/internal.rs +++ b/src/runtime/msg/internal.rs @@ -58,6 +58,10 @@ pub enum Internal { stream_request: Option, meta_request: Option, }, + /// Dispatched when requesting search on catalogs. + CatalogsWithExtraSearch { + query: String, + }, /// Dispatched when library item needs to be updated in the memory, storage and API. UpdateLibraryItem(LibraryItem), /// Dispatched when some of auth, addons or settings changed. @@ -66,9 +70,13 @@ pub enum Internal { LibraryChanged(bool), /// Dispatched when streams bucket changes with a flag if its already persisted. StreamsChanged(bool), + /// Search history has changed. + SearchHistoryChanged, /// User notifications have changed NotificationsChanged, /// Dismiss all Notifications for a given [`MetaItemId`]. + /// + /// [`MetaItemId`]: crate::types::resource::MetaItemId DismissNotificationItem(LibraryItemId), /// Result for loading link code. LinkCodeResult(Result), diff --git a/src/types/mod.rs b/src/types/mod.rs index 721e1707d..b1111e468 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -4,6 +4,7 @@ pub mod library; pub mod notifications; pub mod profile; pub mod resource; +pub mod search_history; pub mod streaming_server; pub mod streams; diff --git a/src/types/resource/stream.rs b/src/types/resource/stream.rs index ff2d702cb..5496bbd86 100644 --- a/src/types/resource/stream.rs +++ b/src/types/resource/stream.rs @@ -136,12 +136,9 @@ impl Stream { } pub fn streaming_url(&self, streaming_server_url: Option<&Url>) -> Option { match (&self.source, streaming_server_url) { - (StreamSource::Url { url }, _) if url.scheme() != "magnet" => { - match (streaming_server_url, &self.behavior_hints.proxy_headers) { - ( - Some(streaming_server_url), - Some(StreamProxyHeaders { request, response }), - ) => { + (StreamSource::Url { url }, Some(streaming_server_url)) if url.scheme() != "magnet" => { + match &self.behavior_hints.proxy_headers { + Some(StreamProxyHeaders { request, response }) => { let mut streaming_url = streaming_server_url.to_owned(); let mut proxy_query = form_urlencoded::Serializer::new(String::new()); let origin = format!("{}://{}", url.scheme(), url.authority()); diff --git a/src/types/search_history/mod.rs b/src/types/search_history/mod.rs new file mode 100644 index 000000000..860f4c1fa --- /dev/null +++ b/src/types/search_history/mod.rs @@ -0,0 +1,2 @@ +mod search_history_bucket; +pub use search_history_bucket::*; diff --git a/src/types/search_history/search_history_bucket.rs b/src/types/search_history/search_history_bucket.rs new file mode 100644 index 000000000..bfc05ffd4 --- /dev/null +++ b/src/types/search_history/search_history_bucket.rs @@ -0,0 +1,21 @@ +use std::collections::HashMap; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::profile::UID; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SearchHistoryBucket { + pub uid: UID, + pub items: HashMap>, +} + +impl SearchHistoryBucket { + pub fn new(uid: UID) -> Self { + Self { + uid, + items: HashMap::new(), + } + } +} diff --git a/src/unit_tests/catalog_with_filters/load_action.rs b/src/unit_tests/catalog_with_filters/load_action.rs index 6dea8fc19..80127f352 100644 --- a/src/unit_tests/catalog_with_filters/load_action.rs +++ b/src/unit_tests/catalog_with_filters/load_action.rs @@ -8,6 +8,7 @@ use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::Profile; use crate::types::resource::MetaItemPreview; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::unit_tests::{ default_fetch_handler, Request, TestEnv, EVENTS, FETCH_HANDLER, REQUESTS, STATES, @@ -49,6 +50,7 @@ fn default_catalog() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ); let (discover, effects) = CatalogWithFilters::::new(&ctx.profile); let (runtime, rx) = Runtime::::new( @@ -148,6 +150,7 @@ fn search_catalog() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ); let (discover, effects) = CatalogWithFilters::::new(&ctx.profile); let (runtime, rx) = Runtime::::new( diff --git a/src/unit_tests/ctx/add_to_library.rs b/src/unit_tests/ctx/add_to_library.rs index 211b23011..367b156e0 100644 --- a/src/unit_tests/ctx/add_to_library.rs +++ b/src/unit_tests/ctx/add_to_library.rs @@ -7,6 +7,7 @@ use crate::types::library::{LibraryBucket, LibraryItem, LibraryItemState}; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Auth, AuthKey, GDPRConsent, Profile, User}; use crate::types::resource::{MetaItemBehaviorHints, MetaItemPreview, PosterShape}; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::types::True; use crate::unit_tests::{ @@ -106,6 +107,7 @@ fn actionctx_addtolibrary() { }, StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -241,6 +243,7 @@ fn actionctx_addtolibrary_already_added() { }, StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/ctx/authenticate.rs b/src/unit_tests/ctx/authenticate.rs index 0f7f91fe4..9015c9fd9 100644 --- a/src/unit_tests/ctx/authenticate.rs +++ b/src/unit_tests/ctx/authenticate.rs @@ -1,4 +1,5 @@ use crate::types::notifications::NotificationsBucket; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::{ constants::{LIBRARY_RECENT_STORAGE_KEY, LIBRARY_STORAGE_KEY, PROFILE_STORAGE_KEY}, @@ -89,6 +90,7 @@ fn actionctx_authenticate_login() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ); let (runtime, _rx) = Runtime::::new(TestModel { ctx }, vec![], 1000); TestEnv::run(|| { @@ -294,6 +296,7 @@ fn actionctx_authenticate_login_with_token() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ); let (runtime, _rx) = Runtime::::new(TestModel { ctx }, vec![], 1000); TestEnv::run(|| { @@ -498,6 +501,7 @@ fn actionctx_authenticate_register() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ); let (runtime, _rx) = Runtime::::new(TestModel { ctx }, vec![], 1000); TestEnv::run(|| { diff --git a/src/unit_tests/ctx/install_addon.rs b/src/unit_tests/ctx/install_addon.rs index 0734d237b..33d8f3d7d 100644 --- a/src/unit_tests/ctx/install_addon.rs +++ b/src/unit_tests/ctx/install_addon.rs @@ -7,6 +7,7 @@ use crate::types::api::{APIResult, SuccessResponse}; use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Auth, AuthKey, GDPRConsent, Profile, User}; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::types::True; use crate::unit_tests::{ @@ -55,6 +56,7 @@ fn actionctx_installaddon_install() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -159,6 +161,7 @@ fn actionctx_installaddon_install_with_user() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -280,6 +283,7 @@ fn actionctx_installaddon_update() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -355,6 +359,7 @@ fn actionctx_installaddon_already_installed() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/ctx/logout.rs b/src/unit_tests/ctx/logout.rs index c90b20923..b8e2ed373 100644 --- a/src/unit_tests/ctx/logout.rs +++ b/src/unit_tests/ctx/logout.rs @@ -6,6 +6,7 @@ use crate::types::api::{APIResult, SuccessResponse}; use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Auth, AuthKey, GDPRConsent, Profile, User}; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::types::True; use crate::unit_tests::{ @@ -85,6 +86,7 @@ fn actionctx_logout() { library, StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/ctx/mod.rs b/src/unit_tests/ctx/mod.rs index 9f081bc47..ec2db8d5b 100644 --- a/src/unit_tests/ctx/mod.rs +++ b/src/unit_tests/ctx/mod.rs @@ -11,5 +11,6 @@ mod remove_from_library; mod rewind_library_item; mod sync_library_with_api; mod uninstall_addon; +mod update_search_history; mod update_settings; mod upgrade_addon; diff --git a/src/unit_tests/ctx/notifications/update_notifications.rs b/src/unit_tests/ctx/notifications/update_notifications.rs index 80916b11c..696dd10a8 100644 --- a/src/unit_tests/ctx/notifications/update_notifications.rs +++ b/src/unit_tests/ctx/notifications/update_notifications.rs @@ -37,6 +37,7 @@ use crate::{ MetaItem, MetaItemId, MetaItemPreview, PosterShape, SeriesInfo, Stream, StreamSource, Video, VideoId, }, + search_history::SearchHistoryBucket, streams::StreamsBucket, }, unit_tests::{ @@ -237,6 +238,7 @@ fn test_pull_notifications_and_play_in_player() { ), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), player: Default::default(), }, @@ -383,6 +385,7 @@ fn test_pull_notifications_test_cases() { LibraryBucket::new(None, test.library_items), StreamsBucket::default(), NotificationsBucket::new::(None, test.notification_items), + SearchHistoryBucket::default(), ), }, vec![], @@ -492,6 +495,7 @@ fn test_dismiss_notification() { }, ], ), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/ctx/pull_addons_from_api.rs b/src/unit_tests/ctx/pull_addons_from_api.rs index f364dbac3..3c67ae1bd 100644 --- a/src/unit_tests/ctx/pull_addons_from_api.rs +++ b/src/unit_tests/ctx/pull_addons_from_api.rs @@ -7,6 +7,7 @@ use crate::types::api::{APIResult, CollectionResponse}; use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Auth, AuthKey, GDPRConsent, Profile, User}; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::unit_tests::{ default_fetch_handler, Request, TestEnv, FETCH_HANDLER, REQUESTS, STORAGE, @@ -43,6 +44,7 @@ fn actionctx_pulladdonsfromapi() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -150,6 +152,7 @@ fn actionctx_pulladdonsfromapi_with_user() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/ctx/push_addons_to_api.rs b/src/unit_tests/ctx/push_addons_to_api.rs index bafb3ecfe..2d2273463 100644 --- a/src/unit_tests/ctx/push_addons_to_api.rs +++ b/src/unit_tests/ctx/push_addons_to_api.rs @@ -6,6 +6,7 @@ use crate::types::api::{APIResult, SuccessResponse}; use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Auth, AuthKey, GDPRConsent, Profile, User}; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::types::True; use crate::unit_tests::{default_fetch_handler, Request, TestEnv, FETCH_HANDLER, REQUESTS}; @@ -51,6 +52,7 @@ fn actionctx_pushaddonstoapi() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -139,6 +141,7 @@ fn actionctx_pushaddonstoapi_with_user() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/ctx/remove_from_library.rs b/src/unit_tests/ctx/remove_from_library.rs index c46d39a9f..fdc2ab8e1 100644 --- a/src/unit_tests/ctx/remove_from_library.rs +++ b/src/unit_tests/ctx/remove_from_library.rs @@ -6,6 +6,7 @@ use crate::types::api::{APIResult, SuccessResponse}; use crate::types::library::{LibraryBucket, LibraryItem}; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Auth, AuthKey, GDPRConsent, Profile, User}; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::types::True; use crate::unit_tests::{ @@ -100,6 +101,7 @@ fn actionctx_removefromlibrary() { }, StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -186,6 +188,7 @@ fn actionctx_removefromlibrary_not_added() { }, StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/ctx/rewind_library_item.rs b/src/unit_tests/ctx/rewind_library_item.rs index bbccf4c0b..62c43a589 100644 --- a/src/unit_tests/ctx/rewind_library_item.rs +++ b/src/unit_tests/ctx/rewind_library_item.rs @@ -6,6 +6,7 @@ use crate::types::api::{APIResult, SuccessResponse}; use crate::types::library::{LibraryBucket, LibraryItem, LibraryItemState}; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Auth, AuthKey, GDPRConsent, Profile, User}; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::types::True; use crate::unit_tests::{ @@ -108,6 +109,7 @@ fn actionctx_rewindlibraryitem() { }, StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -197,6 +199,7 @@ fn actionctx_rewindlibraryitem_not_added() { }, StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/ctx/sync_library_with_api.rs b/src/unit_tests/ctx/sync_library_with_api.rs index 51e4abed3..f84d73b0f 100644 --- a/src/unit_tests/ctx/sync_library_with_api.rs +++ b/src/unit_tests/ctx/sync_library_with_api.rs @@ -6,6 +6,7 @@ use crate::types::api::{APIResult, LibraryItemModified, LibraryItemsResponse, Su use crate::types::library::{LibraryBucket, LibraryItem}; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Auth, AuthKey, GDPRConsent, Profile, User}; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::types::True; use crate::unit_tests::{ @@ -32,6 +33,7 @@ fn actionctx_synclibrarywithapi() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ); let (runtime, _rx) = Runtime::::new(TestModel { ctx }, vec![], 1000); TestEnv::run(|| { @@ -290,6 +292,7 @@ fn actionctx_synclibrarywithapi_with_user() { }, StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -426,6 +429,7 @@ fn actionctx_synclibrarywithapi_with_user_empty_library() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/ctx/uninstall_addon.rs b/src/unit_tests/ctx/uninstall_addon.rs index 5c1f4a8e8..d7943cac5 100644 --- a/src/unit_tests/ctx/uninstall_addon.rs +++ b/src/unit_tests/ctx/uninstall_addon.rs @@ -8,6 +8,7 @@ use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Auth, AuthKey, GDPRConsent, Profile, User}; use crate::types::resource::{Stream, StreamBehaviorHints, StreamSource}; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::{StreamsBucket, StreamsItem, StreamsItemKey}; use crate::types::True; use crate::unit_tests::{ @@ -106,6 +107,7 @@ fn actionctx_uninstalladdon() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -217,6 +219,7 @@ fn actionctx_uninstalladdon_with_user() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -308,6 +311,7 @@ fn actionctx_uninstalladdon_protected() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -382,6 +386,7 @@ fn actionctx_uninstalladdon_not_installed() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -464,6 +469,7 @@ fn actionctx_uninstalladdon_streams_bucket() { LibraryBucket::default(), streams, NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/ctx/update_search_history.rs b/src/unit_tests/ctx/update_search_history.rs new file mode 100644 index 000000000..adbf7eec0 --- /dev/null +++ b/src/unit_tests/ctx/update_search_history.rs @@ -0,0 +1,157 @@ +use stremio_derive::Model; + +use crate::{ + constants::SEARCH_HISTORY_STORAGE_KEY, + models::{ + catalogs_with_extra::{CatalogsWithExtra, Selected}, + ctx::Ctx, + }, + runtime::{ + msg::{Action, ActionCtx, ActionLoad}, + Env, Runtime, RuntimeAction, + }, + types::{ + addon::ExtraValue, library::LibraryBucket, notifications::NotificationsBucket, + profile::Profile, search_history::SearchHistoryBucket, streams::StreamsBucket, + }, + unit_tests::{TestEnv, STORAGE}, +}; + +#[test] +fn test_search_history_update() { + #[derive(Model, Clone, Debug)] + #[model(TestEnv)] + struct TestModel { + ctx: Ctx, + catalogs_with_extra: CatalogsWithExtra, + } + + let _env_mutex = TestEnv::reset().expect("Should have exclusive lock to TestEnv"); + + let ctx = Ctx::new( + Profile::default(), + LibraryBucket::default(), + StreamsBucket::default(), + NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), + ); + + let catalogs_with_extra = CatalogsWithExtra::default(); + + STORAGE.write().unwrap().insert( + SEARCH_HISTORY_STORAGE_KEY.to_owned(), + serde_json::to_string(&ctx.search_history).unwrap(), + ); + + let (runtime, _rx) = Runtime::::new( + TestModel { + ctx, + catalogs_with_extra, + }, + vec![], + 1000, + ); + + let query = "superman"; + let date = TestEnv::now(); + + TestEnv::run(|| { + runtime.dispatch(RuntimeAction { + field: None, + action: Action::Load(ActionLoad::CatalogsWithExtra(Selected { + r#type: None, + extra: vec![ExtraValue { + name: "search".to_owned(), + value: query.to_owned(), + }], + })), + }) + }); + + assert_eq!( + runtime.model().unwrap().ctx.search_history.items.get(query), + Some(date).as_ref(), + "Should have updated search history" + ); + + assert!( + STORAGE + .read() + .unwrap() + .get(SEARCH_HISTORY_STORAGE_KEY) + .map_or(false, |data| { + serde_json::from_str::(data) + .unwrap() + .items + .get(query) + .is_some() + }), + "Should have stored updated search history" + ); +} + +#[test] +fn test_search_history_clear_items() { + #[derive(Model, Clone, Debug)] + #[model(TestEnv)] + struct TestModel { + ctx: Ctx, + catalogs_with_extra: CatalogsWithExtra, + } + + let _env_mutex = TestEnv::reset().expect("Should have exclusive lock to TestEnv"); + + let ctx = Ctx::new( + Profile::default(), + LibraryBucket::default(), + StreamsBucket::default(), + NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), + ); + + let catalogs_with_extra = CatalogsWithExtra::default(); + + STORAGE.write().unwrap().insert( + SEARCH_HISTORY_STORAGE_KEY.to_owned(), + serde_json::to_string(&ctx.search_history).unwrap(), + ); + + let (runtime, _rx) = Runtime::::new( + TestModel { + ctx, + catalogs_with_extra, + }, + vec![], + 1000, + ); + + TestEnv::run(|| { + runtime.dispatch(RuntimeAction { + field: None, + action: Action::Load(ActionLoad::CatalogsWithExtra(Selected { + r#type: None, + extra: vec![ExtraValue { + name: "search".to_owned(), + value: "superman".to_owned(), + }], + })), + }) + }); + + assert!( + !runtime.model().unwrap().ctx.search_history.items.is_empty(), + "Should have updated search history" + ); + + TestEnv::run(|| { + runtime.dispatch(RuntimeAction { + field: None, + action: Action::Ctx(ActionCtx::ClearSearchHistory), + }) + }); + + assert!( + runtime.model().unwrap().ctx.search_history.items.is_empty(), + "Should have cleared search history" + ); +} diff --git a/src/unit_tests/ctx/update_settings.rs b/src/unit_tests/ctx/update_settings.rs index 99c7d2a65..60cf84f66 100644 --- a/src/unit_tests/ctx/update_settings.rs +++ b/src/unit_tests/ctx/update_settings.rs @@ -5,6 +5,7 @@ use crate::runtime::{Runtime, RuntimeAction}; use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Profile, Settings}; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::unit_tests::{TestEnv, REQUESTS, STORAGE}; use stremio_derive::Model; @@ -27,6 +28,7 @@ fn actionctx_updatesettings() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ); let (runtime, _rx) = Runtime::::new(TestModel { ctx }, vec![], 1000); TestEnv::run(|| { @@ -84,6 +86,7 @@ fn actionctx_updatesettings_not_changed() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/ctx/upgrade_addon.rs b/src/unit_tests/ctx/upgrade_addon.rs index 525fbc7bf..53a019538 100644 --- a/src/unit_tests/ctx/upgrade_addon.rs +++ b/src/unit_tests/ctx/upgrade_addon.rs @@ -6,6 +6,7 @@ use crate::types::addon::{Descriptor, Manifest}; use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::Profile; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::unit_tests::{TestEnv, REQUESTS, STORAGE}; use semver::Version; @@ -87,6 +88,7 @@ fn actionctx_addon_upgrade() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], @@ -177,6 +179,7 @@ fn actionctx_addon_upgrade_fail_due_to_different_url() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), }, vec![], diff --git a/src/unit_tests/data_export.rs b/src/unit_tests/data_export.rs index 753ebc1b5..a5e6131a8 100644 --- a/src/unit_tests/data_export.rs +++ b/src/unit_tests/data_export.rs @@ -8,6 +8,7 @@ use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::Profile; use crate::types::profile::{Auth, AuthKey, User}; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::unit_tests::{ default_fetch_handler, Request, TestEnv, EVENTS, FETCH_HANDLER, REQUESTS, STATES, @@ -54,6 +55,7 @@ fn data_export_with_user() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ); ctx.profile.auth = Some(Auth { key: AuthKey("user_key".into()), @@ -134,6 +136,7 @@ fn data_export_without_a_user() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ); assert!( diff --git a/src/unit_tests/deep_links/external_player_link.rs b/src/unit_tests/deep_links/external_player_link.rs index 8b02be7e2..abe1ab695 100644 --- a/src/unit_tests/deep_links/external_player_link.rs +++ b/src/unit_tests/deep_links/external_player_link.rs @@ -11,7 +11,7 @@ use url::Url; const MAGNET_STR_URL: &str = "magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&tr=http%3A%2F%2Fbt1.archive.org%3A6969%2Fannounce"; const HTTP_STR_URL: &str = "http://domain.root/path"; const BASE64_HTTP_URL: &str = "data:application/octet-stream;charset=utf-8;base64,I0VYVE0zVQojRVhUSU5GOjAKaHR0cDovL2RvbWFpbi5yb290L3BhdGg="; -const STREAMING_SERVER_URL: &str = "http://127.0.0.1:11471"; +const STREAMING_SERVER_URL: &str = "http://127.0.0.1:11470"; #[test] fn external_player_link_magnet() { diff --git a/src/unit_tests/deep_links/stream_deep_links.rs b/src/unit_tests/deep_links/stream_deep_links.rs index 5612371cf..4ffdaffb6 100644 --- a/src/unit_tests/deep_links/stream_deep_links.rs +++ b/src/unit_tests/deep_links/stream_deep_links.rs @@ -14,7 +14,7 @@ const MAGNET_STR_URL: &str = "magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d870 const HTTP_STR_URL: &str = "http://domain.root/path"; const HTTP_WITH_QUERY_STR_URL: &str = "http://domain.root/path?param=some&foo=bar"; const BASE64_HTTP_URL: &str = "data:application/octet-stream;charset=utf-8;base64,I0VYVE0zVQojRVhUSU5GOjAKaHR0cDovL2RvbWFpbi5yb290L3BhdGg="; -const STREAMING_SERVER_URL: &str = "http://127.0.0.1:11471"; +const STREAMING_SERVER_URL: &str = "http://127.0.0.1:11470"; const YT_ID: &str = "aqz-KE-bpKQ"; #[test] diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index 6307e913a..7fa0e419a 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -8,7 +8,7 @@ use std::convert::TryFrom; use std::str::FromStr; use url::Url; -const STREAMING_SERVER_URL: &str = "http://127.0.0.1:11471/"; +const STREAMING_SERVER_URL: &str = "http://127.0.0.1:11470/"; const YT_ID: &str = "aqz-KE-bpKQ"; #[test] diff --git a/src/unit_tests/link.rs b/src/unit_tests/link.rs index f4a1aa9f7..26c4e9f9e 100644 --- a/src/unit_tests/link.rs +++ b/src/unit_tests/link.rs @@ -7,6 +7,7 @@ use crate::types::api::{APIResult, LinkAuthKey, LinkCodeResponse, LinkDataRespon use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::Profile; +use crate::types::search_history::SearchHistoryBucket; use crate::types::streams::StreamsBucket; use crate::unit_tests::{default_fetch_handler, Request, TestEnv, FETCH_HANDLER, REQUESTS}; use futures::future; @@ -62,6 +63,7 @@ fn create_link_code() { LibraryBucket::default(), StreamsBucket::default(), NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), ), link: Link::default(), };