From 7dc26a466ae4cf282b33f46295c902c02a53bc13 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 29 Apr 2023 13:08:05 +0200 Subject: [PATCH 01/61] feat(deep_links): add vlc links to ExternalPlayerLink --- src/deep_links/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index f25e0e2c6..cb7965a43 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -12,12 +12,20 @@ use percent_encoding::utf8_percent_encode; use serde::Serialize; use url::Url; +#[derive(Default, Serialize, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct VlcLink { + pub ios: String, + pub android: String, +} + #[derive(Default, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ExternalPlayerLink { pub href: Option, pub download: Option, pub streaming: Option, + pub vlc: Option, pub android_tv: Option, pub tizen: Option, pub webos: Option, @@ -31,6 +39,12 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { let m3u_uri = stream.m3u_data_uri(streaming_server_url.as_ref()); let file_name = m3u_uri.as_ref().map(|_| "playlist.m3u".to_owned()); let href = m3u_uri.or_else(|| download.to_owned()); + let vlc = streaming.as_ref().map(|url| VlcLink { + ios: format!("vlc-x-callback://x-callback-url/stream?url={url}"), + android: format!( + "intent://{url}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", + ), + }); let (android_tv, tizen, webos) = match &stream.source { StreamSource::External { android_tv_url, @@ -48,6 +62,7 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { href, download, streaming, + vlc, android_tv, tizen, webos, From 094b9fba5c381453f081f1e686145fc801ac90f1 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 29 Apr 2023 13:16:27 +0200 Subject: [PATCH 02/61] test(video_deep_links): handle vlc links --- src/unit_tests/deep_links/video_deep_links.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index ef5d049d0..31a0023dd 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -1,5 +1,5 @@ use crate::constants::BASE64; -use crate::deep_links::{ExternalPlayerLink, VideoDeepLinks}; +use crate::deep_links::{ExternalPlayerLink, VideoDeepLinks, VlcLink}; use crate::types::addon::{ResourcePath, ResourceRequest}; use crate::types::resource::Video; use base64::Engine; @@ -52,6 +52,16 @@ fn video_deep_links() { download: Some(format!("https://youtube.com/watch?v={}", YT_ID)), streaming: Some(format!("{}yt/{}", STREAMING_SERVER_URL, YT_ID)), file_name: Some("playlist.m3u".to_string()), + vlc: Some(VlcLink { + ios: format!( + "vlc-x-callback://x-callback-url/stream?url={}yt/{}", + STREAMING_SERVER_URL, YT_ID, + ), + android: format!( + "intent://{}yt/{}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", + STREAMING_SERVER_URL, YT_ID, + ), + }), ..Default::default() }) ); From 52e66c3b9dbffa72a8f5e0a4680bdb6289b660c6 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Wed, 3 May 2023 21:20:24 +0300 Subject: [PATCH 03/61] send diff of transport_urls in AddonsPulledFromAPI --- src/models/ctx/update_profile.rs | 185 ++++++++++++++++--------------- src/types/addon/descriptor.rs | 4 +- src/types/addon/manifest.rs | 16 +-- 3 files changed, 105 insertions(+), 100 deletions(-) diff --git a/src/models/ctx/update_profile.rs b/src/models/ctx/update_profile.rs index 3c10e8e31..7bb318e78 100644 --- a/src/models/ctx/update_profile.rs +++ b/src/models/ctx/update_profile.rs @@ -9,6 +9,7 @@ use crate::types::api::{ use crate::types::profile::{Auth, AuthKey, Profile, Settings, User}; use enclose::enclose; use futures::{future, FutureExt, TryFutureExt}; +use std::collections::HashSet; pub fn update_profile( profile: &mut Profile, @@ -82,10 +83,14 @@ pub fn update_profile( .unwrap_or_else(|| profile_addon.to_owned()) }) .collect::>(); - let transport_urls = next_addons - .iter() - .map(|addon| &addon.transport_url) - .cloned() + let local_addons = profile.addons.iter().cloned().collect::>(); + let remote_addons = next_addons.iter().cloned().collect::>(); + let added_addons = &remote_addons - &local_addons; + let removed_addon = &local_addons - &remote_addons; + let transport_urls = added_addons + .into_iter() + .chain(removed_addon.into_iter()) + .map(|addon| addon.transport_url) .collect(); if profile.addons != next_addons { profile.addons = next_addons; @@ -101,54 +106,38 @@ pub fn update_profile( Effects::msg(Msg::Internal(Internal::InstallAddon(addon.to_owned()))).unchanged() } Msg::Action(Action::Ctx(ActionCtx::UpgradeAddon(addon))) => { - if !profile.addons.contains(addon) { - if !addon.manifest.behavior_hints.configuration_required { - let addon_positions = profile - .addons - .iter() - .map(|addon| &addon.manifest.id) - .enumerate() - .filter(|(_, id)| **id == addon.manifest.id && !addon.flags.protected) - .map(|(position, _)| position) - .collect::>(); - for position in addon_positions { - profile.addons.remove(position); - } - profile.addons.push(addon.to_owned()); - let push_to_api_effects = match profile.auth_key() { - Some(auth_key) => Effects::one(push_addons_to_api::( - profile.addons.to_owned(), - auth_key, - )) - .unchanged(), - _ => Effects::none().unchanged(), - }; - Effects::msg(Msg::Event(Event::AddonUpgraded { - transport_url: addon.transport_url.to_owned(), - id: addon.manifest.id.to_owned(), - })) - .join(push_to_api_effects) - .join(Effects::msg(Msg::Internal(Internal::ProfileChanged))) - } else { - Effects::msg(Msg::Event(Event::Error { - error: CtxError::from(OtherError::AddonConfigurationRequired), - source: Box::new(Event::AddonUpgraded { - transport_url: addon.transport_url.to_owned(), - id: addon.manifest.id.to_owned(), - }), - })) - .unchanged() - } - } else { - Effects::msg(Msg::Event(Event::Error { - error: CtxError::from(OtherError::AddonAlreadyInstalled), - source: Box::new(Event::AddonUpgraded { - transport_url: addon.transport_url.to_owned(), - id: addon.manifest.id.to_owned(), - }), - })) - .unchanged() + if profile.addons.contains(addon) { + return addon_upgrade_error_effects(addon, OtherError::AddonAlreadyInstalled); + } + if addon.manifest.behavior_hints.configuration_required { + return addon_upgrade_error_effects(addon, OtherError::AddonConfigurationRequired); } + let addon_position = match profile + .addons + .iter() + .map(|addon| &addon.transport_url) + .position(|transport_url| *transport_url == addon.transport_url) + { + Some(addon_position) => addon_position, + None => return addon_upgrade_error_effects(addon, OtherError::AddonNotInstalled), + }; + if addon.flags.protected || profile.addons[addon_position].flags.protected { + return addon_upgrade_error_effects(addon, OtherError::AddonIsProtected); + } + profile.addons[addon_position] = addon.to_owned(); + let push_to_api_effects = match profile.auth_key() { + Some(auth_key) => { + Effects::one(push_addons_to_api::(profile.addons.to_owned(), auth_key)) + .unchanged() + } + _ => Effects::none().unchanged(), + }; + Effects::msg(Msg::Event(Event::AddonUpgraded { + transport_url: addon.transport_url.to_owned(), + id: addon.manifest.id.to_owned(), + })) + .join(push_to_api_effects) + .join(Effects::msg(Msg::Internal(Internal::ProfileChanged))) } Msg::Action(Action::Ctx(ActionCtx::UninstallAddon(addon))) => { let addon_position = profile @@ -157,7 +146,7 @@ pub fn update_profile( .map(|addon| &addon.transport_url) .position(|transport_url| *transport_url == addon.transport_url); if let Some(addon_position) = addon_position { - if !profile.addons[addon_position].flags.protected { + if !profile.addons[addon_position].flags.protected && !addon.flags.protected { profile.addons.remove(addon_position); let push_to_api_effects = match profile.auth_key() { Some(auth_key) => Effects::one(push_addons_to_api::( @@ -174,24 +163,10 @@ pub fn update_profile( .join(push_to_api_effects) .join(Effects::msg(Msg::Internal(Internal::ProfileChanged))) } else { - Effects::msg(Msg::Event(Event::Error { - error: CtxError::from(OtherError::AddonIsProtected), - source: Box::new(Event::AddonUninstalled { - transport_url: addon.transport_url.to_owned(), - id: addon.manifest.id.to_owned(), - }), - })) - .unchanged() + addon_uninstall_error_effects(addon, OtherError::AddonIsProtected) } } else { - Effects::msg(Msg::Event(Event::Error { - error: CtxError::from(OtherError::AddonNotInstalled), - source: Box::new(Event::AddonUninstalled { - transport_url: addon.transport_url.to_owned(), - id: addon.manifest.id.to_owned(), - }), - })) - .unchanged() + addon_uninstall_error_effects(addon, OtherError::AddonNotInstalled) } } Msg::Action(Action::Ctx(ActionCtx::LogoutTrakt)) => match &mut profile.auth { @@ -259,37 +234,23 @@ pub fn update_profile( .join(push_to_api_effects) .join(Effects::msg(Msg::Internal(Internal::ProfileChanged))) } else { - Effects::msg(Msg::Event(Event::Error { - error: CtxError::from(OtherError::AddonConfigurationRequired), - source: Box::new(Event::AddonInstalled { - transport_url: addon.transport_url.to_owned(), - id: addon.manifest.id.to_owned(), - }), - })) - .unchanged() + addon_install_error_effects(addon, OtherError::AddonConfigurationRequired) } } else { - Effects::msg(Msg::Event(Event::Error { - error: CtxError::from(OtherError::AddonAlreadyInstalled), - source: Box::new(Event::AddonInstalled { - transport_url: addon.transport_url.to_owned(), - id: addon.manifest.id.to_owned(), - }), - })) - .unchanged() + addon_install_error_effects(addon, OtherError::AddonAlreadyInstalled) } } Msg::Internal(Internal::CtxAuthResult(auth_request, result)) => match (status, result) { (CtxStatus::Loading(loading_auth_request), Ok((auth, addons, _))) if loading_auth_request == auth_request => { - let next_proifle = Profile { + let next_profile = Profile { auth: Some(auth.to_owned()), addons: addons.to_owned(), settings: Settings::default(), }; - if *profile != next_proifle { - *profile = next_proifle; + if *profile != next_profile { + *profile = next_profile; Effects::msg(Msg::Internal(Internal::ProfileChanged)) } else { Effects::none().unchanged() @@ -302,10 +263,14 @@ pub fn update_profile( result, )) if profile.auth_key() == Some(auth_key) => match result { Ok(addons) => { - let transport_urls = addons - .iter() - .map(|addon| &addon.transport_url) - .cloned() + let local_addons = profile.addons.iter().cloned().collect::>(); + let remote_addons = addons.iter().cloned().collect::>(); + let added_addons = &remote_addons - &local_addons; + let removed_addon = &local_addons - &remote_addons; + let transport_urls = added_addons + .into_iter() + .chain(removed_addon.into_iter()) + .map(|addon| addon.transport_url) .collect(); if profile.addons != *addons { profile.addons = addons.to_owned(); @@ -461,3 +426,41 @@ fn push_profile_to_storage(profile: &Profile) -> Effect { ) .into() } + +fn addon_upgrade_error_effects(addon: &Descriptor, error: OtherError) -> Effects { + addon_action_error_effects( + error, + Event::AddonUpgraded { + transport_url: addon.transport_url.to_owned(), + id: addon.manifest.id.to_owned(), + }, + ) +} + +fn addon_uninstall_error_effects(addon: &Descriptor, error: OtherError) -> Effects { + addon_action_error_effects( + error, + Event::AddonUninstalled { + transport_url: addon.transport_url.to_owned(), + id: addon.manifest.id.to_owned(), + }, + ) +} + +fn addon_install_error_effects(addon: &Descriptor, error: OtherError) -> Effects { + addon_action_error_effects( + error, + Event::AddonInstalled { + transport_url: addon.transport_url.to_owned(), + id: addon.manifest.id.to_owned(), + }, + ) +} + +fn addon_action_error_effects(error: OtherError, source: Event) -> Effects { + Effects::msg(Msg::Event(Event::Error { + error: CtxError::from(error), + source: Box::new(source), + })) + .unchanged() +} diff --git a/src/types/addon/descriptor.rs b/src/types/addon/descriptor.rs index fc6618677..e4e7d1d26 100644 --- a/src/types/addon/descriptor.rs +++ b/src/types/addon/descriptor.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use url::Url; /// Addon descriptor -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Descriptor { pub manifest: Manifest, @@ -19,7 +19,7 @@ pub struct DescriptorPreview { pub transport_url: Url, } -#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct DescriptorFlags { #[serde(default)] diff --git a/src/types/addon/manifest.rs b/src/types/addon/manifest.rs index b2bb66bcc..93d6c00f6 100644 --- a/src/types/addon/manifest.rs +++ b/src/types/addon/manifest.rs @@ -11,7 +11,7 @@ use std::borrow::Cow; use url::Url; #[serde_as] -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] #[cfg_attr(test, derive(Derivative))] #[cfg_attr(test, derivative(Default))] #[serde(rename_all = "camelCase")] @@ -99,9 +99,11 @@ pub struct ManifestPreview { #[serde_as(deserialize_as = "DefaultOnError")] pub background: Option, pub types: Vec, + #[serde(default)] + pub behavior_hints: ManifestBehaviorHints, } -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] #[serde(untagged)] pub enum ManifestResource { Short(String), @@ -123,7 +125,7 @@ impl ManifestResource { } } -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ManifestCatalog { pub id: String, @@ -176,7 +178,7 @@ impl UniqueVecAdapter for ManifestCatalogUniqueVecAdapter { } #[serde_as] -#[derive(Derivative, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Derivative, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derivative(Default)] #[serde(untagged)] pub enum ManifestExtra { @@ -218,7 +220,7 @@ impl ManifestExtra { } #[serde_as] -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ExtraProp { pub name: String, @@ -265,7 +267,7 @@ impl<'de> DeserializeAs<'de, ExtraProp> for ExtraPropValid { } } -#[derive(Clone, Deref, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Clone, Deref, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] pub struct OptionsLimit(pub usize); impl Default for OptionsLimit { @@ -274,7 +276,7 @@ impl Default for OptionsLimit { } } -#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[derive(Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ManifestBehaviorHints { #[serde(default)] From 3533ea09146165d5e2c1fec6c17d313a676d394a Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 6 May 2023 20:14:15 +0200 Subject: [PATCH 04/61] feat: add play_in_vlc setting --- src/constants.rs | 2 +- src/runtime/env.rs | 72 +++++++++++++++++++++- src/types/profile/settings.rs | 2 + src/unit_tests/serde/default_tokens_ext.rs | 4 +- src/unit_tests/serde/settings.rs | 9 ++- 5 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 1be6e7d5c..ba53c0177 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -21,7 +21,7 @@ pub const CATALOG_PREVIEW_SIZE: usize = 100; pub const LIBRARY_RECENT_COUNT: usize = 200; pub const WATCHED_THRESHOLD_COEF: f64 = 0.7; pub const CREDITS_THRESHOLD_COEF: f64 = 0.9; -pub const SCHEMA_VERSION: u32 = 7; +pub const SCHEMA_VERSION: u32 = 8; pub const IMDB_LINK_CATEGORY: &str = "imdb"; pub const GENRES_LINK_CATEGORY: &str = "Genres"; pub const CINEMETA_TOP_CATALOG_ID: &str = "top"; diff --git a/src/runtime/env.rs b/src/runtime/env.rs index 9bb72566e..1362c78e5 100644 --- a/src/runtime/env.rs +++ b/src/runtime/env.rs @@ -220,6 +220,12 @@ pub trait Env { .await?; schema_version = 7; }; + if schema_version == 7 { + migrate_storage_schema_to_v8::() + .map_err(|error| EnvError::StorageSchemaVersionUpgrade(Box::new(error))) + .await?; + schema_version = 8; + }; if schema_version != SCHEMA_VERSION { panic!( "Storage schema version must be upgraded from {} to {}", @@ -444,12 +450,35 @@ fn migrate_storage_schema_to_v7() -> TryEnvFuture<()> { .boxed_env() } +fn migrate_storage_schema_to_v8() -> TryEnvFuture<()> { + E::get_storage::(PROFILE_STORAGE_KEY) + .and_then(|mut profile| { + match profile + .as_mut() + .and_then(|profile| profile.as_object_mut()) + .and_then(|profile| profile.get_mut("settings")) + .and_then(|settings| settings.as_object_mut()) + { + Some(settings) => { + settings.insert( + "playInVlc".to_owned(), + serde_json::Value::Bool(false), + ); + E::set_storage(PROFILE_STORAGE_KEY, Some(&profile)) + } + _ => E::set_storage::<()>(PROFILE_STORAGE_KEY, None), + } + }) + .and_then(|_| E::set_storage(SCHEMA_VERSION_STORAGE_KEY, Some(&8))) + .boxed_env() +} + #[cfg(test)] mod test { use serde_json::{json, Value}; use crate::constants::SCHEMA_VERSION; - use crate::runtime::env::{migrate_storage_schema_to_v6, migrate_storage_schema_to_v7}; + use crate::runtime::env::{migrate_storage_schema_to_v6, migrate_storage_schema_to_v7, migrate_storage_schema_to_v8}; use crate::{ constants::{PROFILE_STORAGE_KEY, SCHEMA_VERSION_STORAGE_KEY}, runtime::Env, @@ -711,4 +740,45 @@ mod test { ); } } + + #[tokio::test] + async fn test_migration_from_7_to_8() { + { + let _test_env_guard = TestEnv::reset().expect("Should lock TestEnv"); + let profile_before = json!({ + "settings": {} + }); + + let migrated_profile = json!({ + "settings": { + "playInVlc": false + } + }); + + // setup storage for migration + set_profile_and_schema_version(&profile_before, 7); + + // migrate storage + migrate_storage_schema_to_v8::() + .await + .expect("Should migrate"); + + let storage = STORAGE.read().expect("Should lock"); + + assert_eq!( + &8.to_string(), + storage + .get(SCHEMA_VERSION_STORAGE_KEY) + .expect("Should have the schema set"), + "Scheme version should now be updated" + ); + assert_eq!( + &migrated_profile.to_string(), + storage + .get(PROFILE_STORAGE_KEY) + .expect("Should have the profile set"), + "Profile should match" + ); + } + } } diff --git a/src/types/profile/settings.rs b/src/types/profile/settings.rs index 9f58ea76c..666b638d8 100644 --- a/src/types/profile/settings.rs +++ b/src/types/profile/settings.rs @@ -11,6 +11,7 @@ pub struct Settings { pub player_type: Option, pub binge_watching: bool, pub play_in_background: bool, + pub play_in_vlc: bool, pub hardware_decoding: bool, pub frame_rate_matching_strategy: FrameRateMatchingStrategy, pub next_video_notification_duration: u32, @@ -43,6 +44,7 @@ impl Default for Settings { player_type: None, binge_watching: true, play_in_background: true, + play_in_vlc: false, hardware_decoding: true, frame_rate_matching_strategy: FrameRateMatchingStrategy::FrameRateOnly, next_video_notification_duration: 35000, diff --git a/src/unit_tests/serde/default_tokens_ext.rs b/src/unit_tests/serde/default_tokens_ext.rs index 15780684f..cb9725f5f 100644 --- a/src/unit_tests/serde/default_tokens_ext.rs +++ b/src/unit_tests/serde/default_tokens_ext.rs @@ -373,7 +373,7 @@ impl DefaultTokens for Settings { vec![ Token::Struct { name: "Settings", - len: 22, + len: 23, }, Token::Str("interfaceLanguage"), Token::Str("eng"), @@ -385,6 +385,8 @@ impl DefaultTokens for Settings { Token::Bool(true), Token::Str("playInBackground"), Token::Bool(true), + Token::Str("playInVlc"), + Token::Bool(false), Token::Str("hardwareDecoding"), Token::Bool(true), Token::Str("frameRateMatchingStrategy"), diff --git a/src/unit_tests/serde/settings.rs b/src/unit_tests/serde/settings.rs index ee683e87e..386785ed5 100644 --- a/src/unit_tests/serde/settings.rs +++ b/src/unit_tests/serde/settings.rs @@ -12,6 +12,7 @@ fn settings() { player_type: Some("player".to_owned()), binge_watching: true, play_in_background: true, + play_in_vlc: false, hardware_decoding: true, frame_rate_matching_strategy: FrameRateMatchingStrategy::FrameRateAndResolution, next_video_notification_duration: 30, @@ -35,7 +36,7 @@ fn settings() { &[ Token::Struct { name: "Settings", - len: 22, + len: 23, }, Token::Str("interfaceLanguage"), Token::Str("interface_language"), @@ -48,6 +49,8 @@ fn settings() { Token::Bool(true), Token::Str("playInBackground"), Token::Bool(true), + Token::Str("playInVlc"), + Token::Bool(false), Token::Str("hardwareDecoding"), Token::Bool(true), Token::Str("frameRateMatchingStrategy"), @@ -100,7 +103,7 @@ fn settings_de() { &[ Token::Struct { name: "Settings", - len: 17, + len: 18, }, Token::Str("interfaceLanguage"), Token::Str("eng"), @@ -112,6 +115,8 @@ fn settings_de() { Token::Bool(true), Token::Str("playInBackground"), Token::Bool(true), + Token::Str("playInVlc"), + Token::Bool(false), Token::Str("hardwareDecoding"), Token::Bool(true), Token::Str("frameRateMatchingStrategy"), From 7c2fabef36cd76f2aeeb23c249d42823f825f98e Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 6 May 2023 20:25:42 +0200 Subject: [PATCH 05/61] fix: lint errors --- src/runtime/env.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/runtime/env.rs b/src/runtime/env.rs index 1362c78e5..a403a5206 100644 --- a/src/runtime/env.rs +++ b/src/runtime/env.rs @@ -460,10 +460,7 @@ fn migrate_storage_schema_to_v8() -> TryEnvFuture<()> { .and_then(|settings| settings.as_object_mut()) { Some(settings) => { - settings.insert( - "playInVlc".to_owned(), - serde_json::Value::Bool(false), - ); + settings.insert("playInVlc".to_owned(), serde_json::Value::Bool(false)); E::set_storage(PROFILE_STORAGE_KEY, Some(&profile)) } _ => E::set_storage::<()>(PROFILE_STORAGE_KEY, None), @@ -478,7 +475,9 @@ mod test { use serde_json::{json, Value}; use crate::constants::SCHEMA_VERSION; - use crate::runtime::env::{migrate_storage_schema_to_v6, migrate_storage_schema_to_v7, migrate_storage_schema_to_v8}; + use crate::runtime::env::{ + migrate_storage_schema_to_v6, migrate_storage_schema_to_v7, migrate_storage_schema_to_v8, + }; use crate::{ constants::{PROFILE_STORAGE_KEY, SCHEMA_VERSION_STORAGE_KEY}, runtime::Env, From 9a17813fe816220bfbc3f5fe4390807d8ecb3beb Mon Sep 17 00:00:00 2001 From: TheBeastLT Date: Mon, 15 May 2023 18:43:17 +0200 Subject: [PATCH 06/61] fix video params logic --- .../http_transport/legacy/mod.rs | 55 +++++++++++++++++-- src/models/player.rs | 51 +++++++++-------- 2 files changed, 77 insertions(+), 29 deletions(-) diff --git a/src/addon_transport/http_transport/legacy/mod.rs b/src/addon_transport/http_transport/legacy/mod.rs index 3214dca3d..eb76bb708 100644 --- a/src/addon_transport/http_transport/legacy/mod.rs +++ b/src/addon_transport/http_transport/legacy/mod.rs @@ -1,5 +1,5 @@ use crate::addon_transport::AddonTransport; -use crate::constants::BASE64; +use crate::constants::{BASE64, VIDEO_HASH_EXTRA_PROP, VIDEO_SIZE_EXTRA_PROP}; use crate::runtime::{ConditionalSend, Env, EnvError, EnvFutureExt, TryEnvFuture}; use crate::types::addon::{Manifest, ResourcePath, ResourceResponse}; use crate::types::resource::{MetaItem, MetaItemPreview, Stream, Subtitles}; @@ -8,6 +8,7 @@ use futures::{future, TryFutureExt}; use http::Request; use serde::Deserialize; use serde_json::json; +use std::collections::HashMap; use std::marker::PhantomData; use url::Url; @@ -199,10 +200,27 @@ fn build_legacy_req(transport_url: &Url, path: &ResourcePath) -> Result build_jsonrpc( - "subtitles.find", - json!({ "query": json!({ "itemHash": id.replace(':', " ") }) }), - ), + "subtitles" => { + let mut query = HashMap::new(); + query.insert("itemHash", serde_json::Value::String(id.replace(':', " "))); + let video_hash = path.get_extra_first_value(VIDEO_HASH_EXTRA_PROP.name.as_str()); + if let Some(video_hash) = video_hash { + query.insert( + VIDEO_HASH_EXTRA_PROP.name.as_str(), + serde_json::Value::String(video_hash.to_owned()), + ); + } + let video_size = path.get_extra_first_value(VIDEO_SIZE_EXTRA_PROP.name.as_str()); + if let Some(video_size) = video_size { + query.insert( + VIDEO_SIZE_EXTRA_PROP.name.as_str(), + serde_json::Value::Number( + video_size.parse().expect("Failed to parse videoSize"), + ), + ); + } + build_jsonrpc("subtitles.find", json!({ "query": query })) + } _ => return Err(LegacyErr::UnsupportedRequest.into()), }; // NOTE: this is not using a URL safe base64 standard, which means that technically this is @@ -263,7 +281,7 @@ fn query_from_id(id: &str) -> serde_json::Value { #[cfg(test)] mod test { use super::*; - use crate::types::addon::ResourcePath; + use crate::types::addon::{ExtraExt, ResourcePath}; // Those are a bit sensitive for now, but that's a good thing, since it will force us // to pay attention to minor details that might matter with the legacy system @@ -290,6 +308,31 @@ mod test { ); } + #[test] + fn subtitles_only_id() { + let transport_url = + Url::parse("https://legacywatchhub.strem.io/stremio/v1").expect("url parse failed"); + let path = ResourcePath::without_extra("subtitles", "series", "tt0386676:5:1"); + assert_eq!( + &build_legacy_req(&transport_url, &path).unwrap().uri().to_string(), + "https://legacywatchhub.strem.io/stremio/v1/q.json?b=eyJpZCI6MSwianNvbnJwYyI6IjIuMCIsIm1ldGhvZCI6InN1YnRpdGxlcy5maW5kIiwicGFyYW1zIjpbbnVsbCx7InF1ZXJ5Ijp7Iml0ZW1IYXNoIjoidHQwMzg2Njc2IDUgMSJ9fV19" + ); + } + + #[test] + fn subtitles_with_hash() { + let transport_url = + Url::parse("https://legacywatchhub.strem.io/stremio/v1").expect("url parse failed"); + let extra = &vec![] + .extend_one(&VIDEO_HASH_EXTRA_PROP, Some("ffffffffff".to_string())) + .extend_one(&VIDEO_SIZE_EXTRA_PROP, Some("1000000000".to_string())); + let path = ResourcePath::with_extra("subtitles", "series", "tt0386676:5:1", extra); + assert_eq!( + &build_legacy_req(&transport_url, &path).unwrap().uri().to_string(), + "https://legacywatchhub.strem.io/stremio/v1/q.json?b=eyJpZCI6MSwianNvbnJwYyI6IjIuMCIsIm1ldGhvZCI6InN1YnRpdGxlcy5maW5kIiwicGFyYW1zIjpbbnVsbCx7InF1ZXJ5Ijp7Iml0ZW1IYXNoIjoidHQwMzg2Njc2IDUgMSIsInZpZGVvSGFzaCI6ImZmZmZmZmZmZmYiLCJ2aWRlb1NpemUiOjEwMDAwMDAwMDB9fV19" + ); + } + #[test] fn query_meta() { assert_eq!( diff --git a/src/models/player.rs b/src/models/player.rs index bbbd81b80..1e2ca0e05 100644 --- a/src/models/player.rs +++ b/src/models/player.rs @@ -142,29 +142,34 @@ impl UpdateWithCtx for Player { }, _ => eq_update(&mut self.meta_item, None), }; - let subtitles_effects = match (&selected.subtitles_path, &selected.video_params) { - (Some(subtitles_path), Some(video_params)) => { - resources_update_with_vector_content::( - &mut self.subtitles, - ResourcesAction::ResourcesRequested { - request: &AggrRequest::AllOfResource(ResourcePath { - extra: subtitles_path - .extra - .to_owned() - .extend_one( - &VIDEO_HASH_EXTRA_PROP, - video_params.hash.to_owned(), - ) - .extend_one( - &VIDEO_SIZE_EXTRA_PROP, - video_params.size.as_ref().map(|size| size.to_string()), - ), - ..subtitles_path.to_owned() - }), - addons: &ctx.profile.addons, - }, - ) - } + let subtitles_effects = match &selected.subtitles_path { + Some(subtitles_path) => resources_update_with_vector_content::( + &mut self.subtitles, + ResourcesAction::ResourcesRequested { + request: &AggrRequest::AllOfResource(ResourcePath { + extra: subtitles_path + .extra + .to_owned() + .extend_one( + &VIDEO_HASH_EXTRA_PROP, + selected + .video_params + .as_ref() + .and_then(|params| params.hash.to_owned()), + ) + .extend_one( + &VIDEO_SIZE_EXTRA_PROP, + selected + .video_params + .as_ref() + .and_then(|params| params.size) + .map(|size| size.to_string()), + ), + ..subtitles_path.to_owned() + }), + addons: &ctx.profile.addons, + }, + ), _ => eq_update(&mut self.subtitles, vec![]), }; let next_video_effects = next_video_update( From 79efed3ba60b60d19ce86bb282a1f7d81ec1b0e3 Mon Sep 17 00:00:00 2001 From: TheBeastLT Date: Tue, 16 May 2023 15:50:46 +0200 Subject: [PATCH 07/61] code review remove expect --- src/addon_transport/http_transport/legacy/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/addon_transport/http_transport/legacy/mod.rs b/src/addon_transport/http_transport/legacy/mod.rs index eb76bb708..b3c6c2cc1 100644 --- a/src/addon_transport/http_transport/legacy/mod.rs +++ b/src/addon_transport/http_transport/legacy/mod.rs @@ -210,13 +210,13 @@ fn build_legacy_req(transport_url: &Url, path: &ResourcePath) -> Result Date: Fri, 19 May 2023 20:43:32 +0200 Subject: [PATCH 08/61] change video size type --- 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 1e2ca0e05..4ad5ac5c8 100644 --- a/src/models/player.rs +++ b/src/models/player.rs @@ -58,7 +58,7 @@ pub struct AnalyticsContext { #[serde(rename_all = "camelCase")] pub struct VideoParams { pub hash: Option, - pub size: Option, + pub size: Option, } #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] From c16ad4fc9844bfe68c1f4504ec177d3e3be59509 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Mon, 29 May 2023 21:56:01 +0300 Subject: [PATCH 09/61] Remove playInVlc Setting --- src/constants.rs | 2 +- src/runtime/env.rs | 71 +--------------------- src/types/profile/settings.rs | 2 - src/unit_tests/serde/default_tokens_ext.rs | 4 +- src/unit_tests/serde/settings.rs | 9 +-- 5 files changed, 5 insertions(+), 83 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index ba53c0177..1be6e7d5c 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -21,7 +21,7 @@ pub const CATALOG_PREVIEW_SIZE: usize = 100; pub const LIBRARY_RECENT_COUNT: usize = 200; pub const WATCHED_THRESHOLD_COEF: f64 = 0.7; pub const CREDITS_THRESHOLD_COEF: f64 = 0.9; -pub const SCHEMA_VERSION: u32 = 8; +pub const SCHEMA_VERSION: u32 = 7; pub const IMDB_LINK_CATEGORY: &str = "imdb"; pub const GENRES_LINK_CATEGORY: &str = "Genres"; pub const CINEMETA_TOP_CATALOG_ID: &str = "top"; diff --git a/src/runtime/env.rs b/src/runtime/env.rs index a403a5206..9bb72566e 100644 --- a/src/runtime/env.rs +++ b/src/runtime/env.rs @@ -220,12 +220,6 @@ pub trait Env { .await?; schema_version = 7; }; - if schema_version == 7 { - migrate_storage_schema_to_v8::() - .map_err(|error| EnvError::StorageSchemaVersionUpgrade(Box::new(error))) - .await?; - schema_version = 8; - }; if schema_version != SCHEMA_VERSION { panic!( "Storage schema version must be upgraded from {} to {}", @@ -450,34 +444,12 @@ fn migrate_storage_schema_to_v7() -> TryEnvFuture<()> { .boxed_env() } -fn migrate_storage_schema_to_v8() -> TryEnvFuture<()> { - E::get_storage::(PROFILE_STORAGE_KEY) - .and_then(|mut profile| { - match profile - .as_mut() - .and_then(|profile| profile.as_object_mut()) - .and_then(|profile| profile.get_mut("settings")) - .and_then(|settings| settings.as_object_mut()) - { - Some(settings) => { - settings.insert("playInVlc".to_owned(), serde_json::Value::Bool(false)); - E::set_storage(PROFILE_STORAGE_KEY, Some(&profile)) - } - _ => E::set_storage::<()>(PROFILE_STORAGE_KEY, None), - } - }) - .and_then(|_| E::set_storage(SCHEMA_VERSION_STORAGE_KEY, Some(&8))) - .boxed_env() -} - #[cfg(test)] mod test { use serde_json::{json, Value}; use crate::constants::SCHEMA_VERSION; - use crate::runtime::env::{ - migrate_storage_schema_to_v6, migrate_storage_schema_to_v7, migrate_storage_schema_to_v8, - }; + use crate::runtime::env::{migrate_storage_schema_to_v6, migrate_storage_schema_to_v7}; use crate::{ constants::{PROFILE_STORAGE_KEY, SCHEMA_VERSION_STORAGE_KEY}, runtime::Env, @@ -739,45 +711,4 @@ mod test { ); } } - - #[tokio::test] - async fn test_migration_from_7_to_8() { - { - let _test_env_guard = TestEnv::reset().expect("Should lock TestEnv"); - let profile_before = json!({ - "settings": {} - }); - - let migrated_profile = json!({ - "settings": { - "playInVlc": false - } - }); - - // setup storage for migration - set_profile_and_schema_version(&profile_before, 7); - - // migrate storage - migrate_storage_schema_to_v8::() - .await - .expect("Should migrate"); - - let storage = STORAGE.read().expect("Should lock"); - - assert_eq!( - &8.to_string(), - storage - .get(SCHEMA_VERSION_STORAGE_KEY) - .expect("Should have the schema set"), - "Scheme version should now be updated" - ); - assert_eq!( - &migrated_profile.to_string(), - storage - .get(PROFILE_STORAGE_KEY) - .expect("Should have the profile set"), - "Profile should match" - ); - } - } } diff --git a/src/types/profile/settings.rs b/src/types/profile/settings.rs index 666b638d8..9f58ea76c 100644 --- a/src/types/profile/settings.rs +++ b/src/types/profile/settings.rs @@ -11,7 +11,6 @@ pub struct Settings { pub player_type: Option, pub binge_watching: bool, pub play_in_background: bool, - pub play_in_vlc: bool, pub hardware_decoding: bool, pub frame_rate_matching_strategy: FrameRateMatchingStrategy, pub next_video_notification_duration: u32, @@ -44,7 +43,6 @@ impl Default for Settings { player_type: None, binge_watching: true, play_in_background: true, - play_in_vlc: false, hardware_decoding: true, frame_rate_matching_strategy: FrameRateMatchingStrategy::FrameRateOnly, next_video_notification_duration: 35000, diff --git a/src/unit_tests/serde/default_tokens_ext.rs b/src/unit_tests/serde/default_tokens_ext.rs index cb9725f5f..15780684f 100644 --- a/src/unit_tests/serde/default_tokens_ext.rs +++ b/src/unit_tests/serde/default_tokens_ext.rs @@ -373,7 +373,7 @@ impl DefaultTokens for Settings { vec![ Token::Struct { name: "Settings", - len: 23, + len: 22, }, Token::Str("interfaceLanguage"), Token::Str("eng"), @@ -385,8 +385,6 @@ impl DefaultTokens for Settings { Token::Bool(true), Token::Str("playInBackground"), Token::Bool(true), - Token::Str("playInVlc"), - Token::Bool(false), Token::Str("hardwareDecoding"), Token::Bool(true), Token::Str("frameRateMatchingStrategy"), diff --git a/src/unit_tests/serde/settings.rs b/src/unit_tests/serde/settings.rs index 386785ed5..ee683e87e 100644 --- a/src/unit_tests/serde/settings.rs +++ b/src/unit_tests/serde/settings.rs @@ -12,7 +12,6 @@ fn settings() { player_type: Some("player".to_owned()), binge_watching: true, play_in_background: true, - play_in_vlc: false, hardware_decoding: true, frame_rate_matching_strategy: FrameRateMatchingStrategy::FrameRateAndResolution, next_video_notification_duration: 30, @@ -36,7 +35,7 @@ fn settings() { &[ Token::Struct { name: "Settings", - len: 23, + len: 22, }, Token::Str("interfaceLanguage"), Token::Str("interface_language"), @@ -49,8 +48,6 @@ fn settings() { Token::Bool(true), Token::Str("playInBackground"), Token::Bool(true), - Token::Str("playInVlc"), - Token::Bool(false), Token::Str("hardwareDecoding"), Token::Bool(true), Token::Str("frameRateMatchingStrategy"), @@ -103,7 +100,7 @@ fn settings_de() { &[ Token::Struct { name: "Settings", - len: 18, + len: 17, }, Token::Str("interfaceLanguage"), Token::Str("eng"), @@ -115,8 +112,6 @@ fn settings_de() { Token::Bool(true), Token::Str("playInBackground"), Token::Bool(true), - Token::Str("playInVlc"), - Token::Bool(false), Token::Str("hardwareDecoding"), Token::Bool(true), Token::Str("frameRateMatchingStrategy"), From 088664cb814bf93c93bdac2b6724a8ff64ffd586 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 11:25:31 +0300 Subject: [PATCH 10/61] Rename `VlcLink` to `OpenPlayerLink` --- src/deep_links/mod.rs | 6 +++--- src/unit_tests/deep_links/video_deep_links.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index cb7965a43..5c28c50b7 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -14,7 +14,7 @@ use url::Url; #[derive(Default, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] -pub struct VlcLink { +pub struct OpenPlayerLink { pub ios: String, pub android: String, } @@ -25,7 +25,7 @@ pub struct ExternalPlayerLink { pub href: Option, pub download: Option, pub streaming: Option, - pub vlc: Option, + pub vlc: Option, pub android_tv: Option, pub tizen: Option, pub webos: Option, @@ -39,7 +39,7 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { let m3u_uri = stream.m3u_data_uri(streaming_server_url.as_ref()); let file_name = m3u_uri.as_ref().map(|_| "playlist.m3u".to_owned()); let href = m3u_uri.or_else(|| download.to_owned()); - let vlc = streaming.as_ref().map(|url| VlcLink { + let vlc = streaming.as_ref().map(|url| OpenPlayerLink { ios: format!("vlc-x-callback://x-callback-url/stream?url={url}"), android: format!( "intent://{url}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index 31a0023dd..c17ececdb 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -1,5 +1,5 @@ use crate::constants::BASE64; -use crate::deep_links::{ExternalPlayerLink, VideoDeepLinks, VlcLink}; +use crate::deep_links::{ExternalPlayerLink, VideoDeepLinks, OpenPlayerLink}; use crate::types::addon::{ResourcePath, ResourceRequest}; use crate::types::resource::Video; use base64::Engine; @@ -52,7 +52,7 @@ fn video_deep_links() { download: Some(format!("https://youtube.com/watch?v={}", YT_ID)), streaming: Some(format!("{}yt/{}", STREAMING_SERVER_URL, YT_ID)), file_name: Some("playlist.m3u".to_string()), - vlc: Some(VlcLink { + vlc: Some(OpenPlayerLink { ios: format!( "vlc-x-callback://x-callback-url/stream?url={}yt/{}", STREAMING_SERVER_URL, YT_ID, From e31841aec0540e971976e6ae9c084e3413dbe214 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 11:44:14 +0300 Subject: [PATCH 11/61] Lint --- src/unit_tests/deep_links/video_deep_links.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index c17ececdb..9ebc34ad7 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -1,5 +1,5 @@ use crate::constants::BASE64; -use crate::deep_links::{ExternalPlayerLink, VideoDeepLinks, OpenPlayerLink}; +use crate::deep_links::{ExternalPlayerLink, OpenPlayerLink, VideoDeepLinks}; use crate::types::addon::{ResourcePath, ResourceRequest}; use crate::types::resource::Video; use base64::Engine; From 27b067fe2cd71f78c709348c5dd6e55d6fde0719 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 13:07:36 +0300 Subject: [PATCH 12/61] Add More External Video Player Options --- src/deep_links/mod.rs | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index 5c28c50b7..289beee29 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -15,8 +15,16 @@ use url::Url; #[derive(Default, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct OpenPlayerLink { - pub ios: String, - pub android: String, + pub ios: Option, + pub android: Option, + pub windows: Option, + pub macos: Option, + pub linux: Option, + pub tizen: Option, + pub webos: Option, + pub chromeos: Option, + pub webos: Option, + pub roku: Option, } #[derive(Default, Serialize, Debug, PartialEq, Eq)] @@ -34,17 +42,40 @@ pub struct ExternalPlayerLink { impl From<(&Stream, &Option)> for ExternalPlayerLink { fn from((stream, streaming_server_url): (&Stream, &Option)) -> Self { + use regex::Regex; + let http_regex = Regex::new(r"https?://").unwrap(); let download = stream.download_url(); let streaming = stream.streaming_url(streaming_server_url.as_ref()); let m3u_uri = stream.m3u_data_uri(streaming_server_url.as_ref()); let file_name = m3u_uri.as_ref().map(|_| "playlist.m3u".to_owned()); let href = m3u_uri.or_else(|| download.to_owned()); + let choose = streaming.as_ref().map(|url| OpenPlayerLink { + android: format!( + "intent:{url}#Intent;type=video/any;scheme=https;end", + ), + }); let vlc = streaming.as_ref().map(|url| OpenPlayerLink { ios: format!("vlc-x-callback://x-callback-url/stream?url={url}"), android: format!( - "intent://{url}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", + "intent:{url}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", + ), + }); + let mxplayer = streaming.as_ref().map(|url| OpenPlayerLink { + android: format!( + "intent:{url}#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=https;end", ), }); + let justplayer = streaming.as_ref().map(|url| OpenPlayerLink { + android: format!( + "intent:{url}#Intent;package=com.brouken.player;type=video;scheme=https;end", + ), + }); + let outplayer = streaming.as_ref().map(|url| OpenPlayerLink { + ios: format!("{}", http_regex.replace(url, "outplayer://")), + }); + let infuse = streaming.as_ref().map(|url| OpenPlayerLink { + ios: format!("{}", http_regex.replace(url, "infuse://")), + }); let (android_tv, tizen, webos) = match &stream.source { StreamSource::External { android_tv_url, From ef34854f81382dfa0233614ab8304fd6a145aa83 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 13:11:45 +0300 Subject: [PATCH 13/61] Lint --- src/deep_links/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index 289beee29..db0598251 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -50,9 +50,7 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { let file_name = m3u_uri.as_ref().map(|_| "playlist.m3u".to_owned()); let href = m3u_uri.or_else(|| download.to_owned()); let choose = streaming.as_ref().map(|url| OpenPlayerLink { - android: format!( - "intent:{url}#Intent;type=video/any;scheme=https;end", - ), + android: format!("intent:{url}#Intent;type=video/any;scheme=https;end",), }); let vlc = streaming.as_ref().map(|url| OpenPlayerLink { ios: format!("vlc-x-callback://x-callback-url/stream?url={url}"), From 1f35a392e6ebfafd84d35aba89d1da63b2df62b2 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 13:17:06 +0300 Subject: [PATCH 14/61] Lint --- src/deep_links/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index db0598251..81432479b 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -11,6 +11,7 @@ use crate::types::resource::{MetaItem, MetaItemPreview, Stream, StreamSource, Vi use percent_encoding::utf8_percent_encode; use serde::Serialize; use url::Url; +use regex::Regex; #[derive(Default, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] @@ -23,7 +24,6 @@ pub struct OpenPlayerLink { pub tizen: Option, pub webos: Option, pub chromeos: Option, - pub webos: Option, pub roku: Option, } @@ -42,7 +42,6 @@ pub struct ExternalPlayerLink { impl From<(&Stream, &Option)> for ExternalPlayerLink { fn from((stream, streaming_server_url): (&Stream, &Option)) -> Self { - use regex::Regex; let http_regex = Regex::new(r"https?://").unwrap(); let download = stream.download_url(); let streaming = stream.streaming_url(streaming_server_url.as_ref()); From a4e0cee14a8e46f4d31a4068f79987f2ecf57b9a Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 13:18:26 +0300 Subject: [PATCH 15/61] Lint --- src/deep_links/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index 81432479b..48676f0d5 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -9,9 +9,9 @@ use crate::types::library::LibraryItem; use crate::types::query_params_encode; use crate::types::resource::{MetaItem, MetaItemPreview, Stream, StreamSource, Video}; use percent_encoding::utf8_percent_encode; +use regex::Regex; use serde::Serialize; use url::Url; -use regex::Regex; #[derive(Default, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] From 019fb38cf7b312aee9df715b3fc75bd7f3b46063 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 13:33:05 +0300 Subject: [PATCH 16/61] Declare regex as Dependency --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index d3bc3621e..f2c5e12aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ itertools = "0.10" magnet-url = "2.0" hex = "0.4" anyhow = "1.0" +regex = "1.8.3" [dev-dependencies] tokio = { version = "1.12", features = ["rt", "macros"] } From f51f2ce907587d54ad5de2e6402fad71aefb640b Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 13:47:42 +0300 Subject: [PATCH 17/61] Fix Build --- src/deep_links/mod.rs | 57 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index 48676f0d5..6121bf464 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -52,26 +52,65 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { android: format!("intent:{url}#Intent;type=video/any;scheme=https;end",), }); let vlc = streaming.as_ref().map(|url| OpenPlayerLink { - ios: format!("vlc-x-callback://x-callback-url/stream?url={url}"), - android: format!( + ios: Some(format!("vlc-x-callback://x-callback-url/stream?url={url}")), + android: Some(format!( "intent:{url}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", - ), + )), + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, }); let mxplayer = streaming.as_ref().map(|url| OpenPlayerLink { - android: format!( + android: Some(format!( "intent:{url}#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=https;end", - ), + )), + ios: None, + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, }); let justplayer = streaming.as_ref().map(|url| OpenPlayerLink { - android: format!( + android: Some(format!( "intent:{url}#Intent;package=com.brouken.player;type=video;scheme=https;end", - ), + )), + ios: None, + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, }); let outplayer = streaming.as_ref().map(|url| OpenPlayerLink { - ios: format!("{}", http_regex.replace(url, "outplayer://")), + ios: Some(format!("{}", http_regex.replace(url, "outplayer://"))), + android: None, + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, }); let infuse = streaming.as_ref().map(|url| OpenPlayerLink { - ios: format!("{}", http_regex.replace(url, "infuse://")), + ios: Some(format!("{}", http_regex.replace(url, "infuse://"))), + android: None, + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, }); let (android_tv, tizen, webos) = match &stream.source { StreamSource::External { From 1124d7568e2859a91d5671887c90085807af0a6f Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 13:51:01 +0300 Subject: [PATCH 18/61] Fix Build 2 --- src/deep_links/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index 6121bf464..fe23cf64c 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -49,7 +49,15 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { let file_name = m3u_uri.as_ref().map(|_| "playlist.m3u".to_owned()); let href = m3u_uri.or_else(|| download.to_owned()); let choose = streaming.as_ref().map(|url| OpenPlayerLink { - android: format!("intent:{url}#Intent;type=video/any;scheme=https;end",), + android: Some(format!("intent:{url}#Intent;type=video/any;scheme=https;end",)), + ios: None, + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, }); let vlc = streaming.as_ref().map(|url| OpenPlayerLink { ios: Some(format!("vlc-x-callback://x-callback-url/stream?url={url}")), From ded2a5b5870fb4c4050c458bd102ec8f8c8f3bc3 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 13:52:35 +0300 Subject: [PATCH 19/61] Lint --- src/deep_links/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index fe23cf64c..517ec6c49 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -49,7 +49,9 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { let file_name = m3u_uri.as_ref().map(|_| "playlist.m3u".to_owned()); let href = m3u_uri.or_else(|| download.to_owned()); let choose = streaming.as_ref().map(|url| OpenPlayerLink { - android: Some(format!("intent:{url}#Intent;type=video/any;scheme=https;end",)), + android: Some(format!( + "intent:{url}#Intent;type=video/any;scheme=https;end", + )), ios: None, windows: None, macos: None, From 84d1995eaa1704c168132b50b579eed73bb5917f Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 13:56:29 +0300 Subject: [PATCH 20/61] Fix Build 3 --- src/deep_links/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index 517ec6c49..a001db2ec 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -140,6 +140,11 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { download, streaming, vlc, + choose, + outplayer, + infuse, + mxplayer, + justplayer, android_tv, tizen, webos, From 84f36b22cc4616006d12cd661d49e3143dba0d6a Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 14:00:02 +0300 Subject: [PATCH 21/61] Fix Build 4 --- src/deep_links/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index a001db2ec..2a4c53cef 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -34,6 +34,11 @@ pub struct ExternalPlayerLink { pub download: Option, pub streaming: Option, pub vlc: Option, + pub choose: Option, + pub outplayer: Option, + pub infuse: Option, + pub mxplayer: Option, + pub justplayer: Option, pub android_tv: Option, pub tizen: Option, pub webos: Option, From 04abc29a1878081260eac1568d0a7672408e4817 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 14:08:57 +0300 Subject: [PATCH 22/61] Fix Unit Tests --- src/unit_tests/deep_links/video_deep_links.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index 9ebc34ad7..3e9bcb69c 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -53,14 +53,21 @@ fn video_deep_links() { streaming: Some(format!("{}yt/{}", STREAMING_SERVER_URL, YT_ID)), file_name: Some("playlist.m3u".to_string()), vlc: Some(OpenPlayerLink { - ios: format!( + ios: Some(format!( "vlc-x-callback://x-callback-url/stream?url={}yt/{}", STREAMING_SERVER_URL, YT_ID, - ), - android: format!( + )), + android: Some(format!( "intent://{}yt/{}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", STREAMING_SERVER_URL, YT_ID, - ), + )), + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, }), ..Default::default() }) From 1e06c4964431404d76a54861ed475be06f9ef239 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 14:14:34 +0300 Subject: [PATCH 23/61] Fix Unit Tests 2 --- src/unit_tests/deep_links/video_deep_links.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index 3e9bcb69c..e38763f1c 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -58,7 +58,7 @@ fn video_deep_links() { STREAMING_SERVER_URL, YT_ID, )), android: Some(format!( - "intent://{}yt/{}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", + "intent:{}yt/{}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", STREAMING_SERVER_URL, YT_ID, )), windows: None, From 13cf760069b50420f725410fee919e90709ceac1 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 14:23:09 +0300 Subject: [PATCH 24/61] Fix Unit Tests 3 --- src/unit_tests/deep_links/video_deep_links.rs | 79 +++++++++++++++++-- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index e38763f1c..d673fb15e 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -3,6 +3,7 @@ use crate::deep_links::{ExternalPlayerLink, OpenPlayerLink, VideoDeepLinks}; use crate::types::addon::{ResourcePath, ResourceRequest}; use crate::types::resource::Video; use base64::Engine; +use regex::Regex; use std::convert::TryFrom; use std::str::FromStr; use url::Url; @@ -12,6 +13,7 @@ const YT_ID: &str = "aqz-KE-bpKQ"; #[test] fn video_deep_links() { + let http_regex = Regex::new(r"https?://").unwrap(); let video = Video { id: format!("yt_id:UCSMOQeBJ2RAnuFungnQOxLg:{YT_ID}"), title: "Big Buck Bunny".to_string(), @@ -27,6 +29,7 @@ fn video_deep_links() { path: ResourcePath::without_extra("meta", "movie", format!("yt_id:{YT_ID}").as_str()), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); + let streaming_server_yt = format!("{}yt/{}", STREAMING_SERVER_URL, YT_ID,) let vdl = VideoDeepLinks::try_from((&video, &request, &streaming_server_url)).unwrap(); assert_eq!( vdl.meta_details_streams, @@ -45,22 +48,86 @@ fn video_deep_links() { href: Some(format!( "data:application/octet-stream;charset=utf-8;base64,{}", BASE64.encode(format!( - "#EXTM3U\n#EXTINF:0\n{}yt/{}", - STREAMING_SERVER_URL, YT_ID + "#EXTM3U\n#EXTINF:0\n{}", + streaming_server_yt )) )), download: Some(format!("https://youtube.com/watch?v={}", YT_ID)), streaming: Some(format!("{}yt/{}", STREAMING_SERVER_URL, YT_ID)), file_name: Some("playlist.m3u".to_string()), + choose: Some(OpenPlayerLink { + android: Some(format!( + "intent:{}#Intent;type=video/any;scheme=https;end", + streaming_server_yt, + )), + ios: None, + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, + }), vlc: Some(OpenPlayerLink { ios: Some(format!( - "vlc-x-callback://x-callback-url/stream?url={}yt/{}", - STREAMING_SERVER_URL, YT_ID, + "vlc-x-callback://x-callback-url/stream?url={}", + streaming_server_yt, + )), + android: Some(format!( + "intent:{}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", + streaming_server_yt, )), + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, + }), + mxplayer: Some(OpenPlayerLink { + android: Some(format!( + "intent:{}#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=https;end", + streaming_server_yt, + )), + ios: None, + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, + }), + justplayer: Some(OpenPlayerLink { android: Some(format!( - "intent:{}yt/{}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", - STREAMING_SERVER_URL, YT_ID, + "intent:{}#Intent;package=com.brouken.player;type=video;scheme=https;end", + streaming_server_yt, )), + ios: None, + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, + }), + outplayer: Some(OpenPlayerLink { + ios: Some(format!("{}", http_regex.replace(streaming_server_yt, "outplayer://"))), + android: None, + windows: None, + macos: None, + linux: None, + tizen: None, + webos: None, + chromeos: None, + roku: None, + }), + infuse = Some(OpenPlayerLink { + ios: Some(format!("{}", http_regex.replace(streaming_server_yt, "infuse://"))), + android: None, windows: None, macos: None, linux: None, From 7ed7e58c22fbdc393ebc4246034cd05de978efb4 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 14:24:53 +0300 Subject: [PATCH 25/61] Lint --- src/unit_tests/deep_links/video_deep_links.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index d673fb15e..578ec33c3 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -29,7 +29,7 @@ fn video_deep_links() { path: ResourcePath::without_extra("meta", "movie", format!("yt_id:{YT_ID}").as_str()), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let streaming_server_yt = format!("{}yt/{}", STREAMING_SERVER_URL, YT_ID,) + let streaming_server_yt = format!("{}yt/{}", STREAMING_SERVER_URL, YT_ID,); let vdl = VideoDeepLinks::try_from((&video, &request, &streaming_server_url)).unwrap(); assert_eq!( vdl.meta_details_streams, From bf32e25a0fb046b9c6b56bb6a6aa884cf01d69dd Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 14:32:18 +0300 Subject: [PATCH 26/61] Lint --- src/unit_tests/deep_links/video_deep_links.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index 578ec33c3..c219aa945 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -115,7 +115,7 @@ fn video_deep_links() { roku: None, }), outplayer: Some(OpenPlayerLink { - ios: Some(format!("{}", http_regex.replace(streaming_server_yt, "outplayer://"))), + ios: Some(format!("{}", http_regex.replace(&streaming_server_yt, "outplayer://"))), android: None, windows: None, macos: None, @@ -125,8 +125,8 @@ fn video_deep_links() { chromeos: None, roku: None, }), - infuse = Some(OpenPlayerLink { - ios: Some(format!("{}", http_regex.replace(streaming_server_yt, "infuse://"))), + infuse: Some(OpenPlayerLink { + ios: Some(format!("{}", http_regex.replace(&streaming_server_yt, "infuse://"))), android: None, windows: None, macos: None, From c1bee50f53450311b953f5502e947ab7dfffc84d Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 30 May 2023 15:03:13 +0200 Subject: [PATCH 27/61] fix: use regex 1.6 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f2c5e12aa..b81e8f35a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ itertools = "0.10" magnet-url = "2.0" hex = "0.4" anyhow = "1.0" -regex = "1.8.3" +regex = "1.6.*" [dev-dependencies] tokio = { version = "1.12", features = ["rt", "macros"] } From bd38927b0f02cac4fde108cda57b7d225fcf6824 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 30 May 2023 15:03:34 +0200 Subject: [PATCH 28/61] refactor: use profile settings to create open player link --- src/deep_links/mod.rs | 161 ++++++++---------- .../deep_links/external_player_link.rs | 19 ++- .../deep_links/stream_deep_links.rs | 21 ++- src/unit_tests/deep_links/video_deep_links.rs | 17 +- 4 files changed, 105 insertions(+), 113 deletions(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index 517ec6c49..d1e0f33f9 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -6,6 +6,7 @@ use crate::models::installed_addons_with_filters::InstalledAddonsRequest; use crate::models::library_with_filters::LibraryRequest; use crate::types::addon::{ExtraValue, ResourcePath, ResourceRequest}; use crate::types::library::LibraryItem; +use crate::types::profile::Settings; use crate::types::query_params_encode; use crate::types::resource::{MetaItem, MetaItemPreview, Stream, StreamSource, Video}; use percent_encoding::utf8_percent_encode; @@ -33,95 +34,63 @@ pub struct ExternalPlayerLink { pub href: Option, pub download: Option, pub streaming: Option, - pub vlc: Option, + pub open_player: Option, pub android_tv: Option, pub tizen: Option, pub webos: Option, pub file_name: Option, } -impl From<(&Stream, &Option)> for ExternalPlayerLink { - fn from((stream, streaming_server_url): (&Stream, &Option)) -> Self { +impl From<(&Stream, &Option, &Settings)> for ExternalPlayerLink { + fn from((stream, streaming_server_url, settings): (&Stream, &Option, &Settings)) -> Self { let http_regex = Regex::new(r"https?://").unwrap(); let download = stream.download_url(); let streaming = stream.streaming_url(streaming_server_url.as_ref()); let m3u_uri = stream.m3u_data_uri(streaming_server_url.as_ref()); let file_name = m3u_uri.as_ref().map(|_| "playlist.m3u".to_owned()); let href = m3u_uri.or_else(|| download.to_owned()); - let choose = streaming.as_ref().map(|url| OpenPlayerLink { - android: Some(format!( - "intent:{url}#Intent;type=video/any;scheme=https;end", - )), - ios: None, - windows: None, - macos: None, - linux: None, - tizen: None, - webos: None, - chromeos: None, - roku: None, - }); - let vlc = streaming.as_ref().map(|url| OpenPlayerLink { - ios: Some(format!("vlc-x-callback://x-callback-url/stream?url={url}")), - android: Some(format!( - "intent:{url}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", - )), - windows: None, - macos: None, - linux: None, - tizen: None, - webos: None, - chromeos: None, - roku: None, - }); - let mxplayer = streaming.as_ref().map(|url| OpenPlayerLink { - android: Some(format!( - "intent:{url}#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=https;end", - )), - ios: None, - windows: None, - macos: None, - linux: None, - tizen: None, - webos: None, - chromeos: None, - roku: None, - }); - let justplayer = streaming.as_ref().map(|url| OpenPlayerLink { - android: Some(format!( - "intent:{url}#Intent;package=com.brouken.player;type=video;scheme=https;end", - )), - ios: None, - windows: None, - macos: None, - linux: None, - tizen: None, - webos: None, - chromeos: None, - roku: None, - }); - let outplayer = streaming.as_ref().map(|url| OpenPlayerLink { - ios: Some(format!("{}", http_regex.replace(url, "outplayer://"))), - android: None, - windows: None, - macos: None, - linux: None, - tizen: None, - webos: None, - chromeos: None, - roku: None, - }); - let infuse = streaming.as_ref().map(|url| OpenPlayerLink { - ios: Some(format!("{}", http_regex.replace(url, "infuse://"))), - android: None, - windows: None, - macos: None, - linux: None, - tizen: None, - webos: None, - chromeos: None, - roku: None, - }); + let open_player = match &streaming { + Some(url) => match settings.player_type.as_ref() { + Some(player_type) => match player_type.as_str() { + "choose" => Some(OpenPlayerLink { + android: Some(format!( + "intent:{url}#Intent;type=video/any;scheme=https;end", + )), + ..Default::default() + }), + "vlc" => Some(OpenPlayerLink { + ios: Some(format!("vlc-x-callback://x-callback-url/stream?url={url}")), + android: Some(format!( + "intent:{url}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", + )), + ..Default::default() + }), + "mxplayer" => Some(OpenPlayerLink { + android: Some(format!( + "intent:{url}#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=https;end", + )), + ..Default::default() + }), + "justplayer" => Some(OpenPlayerLink { + android: Some(format!( + "intent:{url}#Intent;package=com.brouken.player;type=video;scheme=https;end", + )), + ..Default::default() + }), + "outplayer" => Some(OpenPlayerLink { + ios: Some(format!("{}", http_regex.replace(url, "outplayer://"))), + ..Default::default() + }), + "infuse" => Some(OpenPlayerLink { + ios: Some(format!("{}", http_regex.replace(url, "infuse://"))), + ..Default::default() + }), + _ => None, + }, + None => None, + }, + None => None, + }; let (android_tv, tizen, webos) = match &stream.source { StreamSource::External { android_tv_url, @@ -139,7 +108,7 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { href, download, streaming, - vlc, + open_player, android_tv, tizen, webos, @@ -275,9 +244,14 @@ pub struct VideoDeepLinks { pub external_player: Option, } -impl From<(&Video, &ResourceRequest, &Option)> for VideoDeepLinks { +impl From<(&Video, &ResourceRequest, &Option, &Settings)> for VideoDeepLinks { fn from( - (video, request, streaming_server_url): (&Video, &ResourceRequest, &Option), + (video, request, streaming_server_url, settings): ( + &Video, + &ResourceRequest, + &Option, + &Settings, + ), ) -> Self { let stream = video.stream(); VideoDeepLinks { @@ -302,9 +276,9 @@ impl From<(&Video, &ResourceRequest, &Option)> for VideoDeepLinks { }) .transpose() .unwrap_or_else(|error| Some(ErrorLink::from(error).into())), - external_player: stream - .as_ref() - .map(|stream| ExternalPlayerLink::from((stream.as_ref(), streaming_server_url))), + external_player: stream.as_ref().map(|stream| { + ExternalPlayerLink::from((stream.as_ref(), streaming_server_url, settings)) + }), } } } @@ -316,8 +290,8 @@ pub struct StreamDeepLinks { pub external_player: ExternalPlayerLink, } -impl From<(&Stream, &Option)> for StreamDeepLinks { - fn from((stream, streaming_server_url): (&Stream, &Option)) -> Self { +impl From<(&Stream, &Option, &Settings)> for StreamDeepLinks { + fn from((stream, streaming_server_url, settings): (&Stream, &Option, &Settings)) -> Self { StreamDeepLinks { player: stream .encode() @@ -328,18 +302,27 @@ impl From<(&Stream, &Option)> for StreamDeepLinks { ) }) .unwrap_or_else(|error| ErrorLink::from(error).into()), - external_player: ExternalPlayerLink::from((stream, streaming_server_url)), + external_player: ExternalPlayerLink::from((stream, streaming_server_url, settings)), } } } -impl From<(&Stream, &ResourceRequest, &ResourceRequest, &Option)> for StreamDeepLinks { +impl + From<( + &Stream, + &ResourceRequest, + &ResourceRequest, + &Option, + &Settings, + )> for StreamDeepLinks +{ fn from( - (stream, stream_request, meta_request, streaming_server_url): ( + (stream, stream_request, meta_request, streaming_server_url, settings): ( &Stream, &ResourceRequest, &ResourceRequest, &Option, + &Settings, ), ) -> Self { StreamDeepLinks { @@ -357,7 +340,7 @@ impl From<(&Stream, &ResourceRequest, &ResourceRequest, &Option)> for Strea ) }) .unwrap_or_else(|error| ErrorLink::from(error).into()), - external_player: ExternalPlayerLink::from((stream, streaming_server_url)), + external_player: ExternalPlayerLink::from((stream, streaming_server_url, settings)), } } } diff --git a/src/unit_tests/deep_links/external_player_link.rs b/src/unit_tests/deep_links/external_player_link.rs index bff80f033..8b02be7e2 100644 --- a/src/unit_tests/deep_links/external_player_link.rs +++ b/src/unit_tests/deep_links/external_player_link.rs @@ -1,5 +1,6 @@ use crate::constants::{BASE64, URI_COMPONENT_ENCODE_SET}; use crate::deep_links::ExternalPlayerLink; +use crate::types::profile::Settings; use crate::types::resource::{Stream, StreamSource}; use base64::Engine; use percent_encoding::utf8_percent_encode; @@ -25,7 +26,8 @@ fn external_player_link_magnet() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(epl.href, Some(MAGNET_STR_URL.to_owned())); assert_eq!(epl.file_name, None); } @@ -43,7 +45,8 @@ fn external_player_link_http() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(epl.href, Some(BASE64_HTTP_URL.to_owned())); assert_eq!(epl.file_name, Some("playlist.m3u".to_string())); } @@ -69,7 +72,8 @@ fn external_player_link_torrent() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!( epl.href, Some(format!( @@ -109,7 +113,8 @@ fn external_player_link_external() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(epl.href, Some(HTTP_STR_URL.to_owned())); assert_eq!(epl.file_name, None); } @@ -128,7 +133,8 @@ fn external_player_link_youtube() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!( epl.href, Some(format!( @@ -155,7 +161,8 @@ fn external_player_link_player_frame() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let epl = ExternalPlayerLink::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(epl.href, Some(HTTP_STR_URL.to_owned())); assert_eq!(epl.file_name, None); } diff --git a/src/unit_tests/deep_links/stream_deep_links.rs b/src/unit_tests/deep_links/stream_deep_links.rs index b15b7c240..d6beebe8f 100644 --- a/src/unit_tests/deep_links/stream_deep_links.rs +++ b/src/unit_tests/deep_links/stream_deep_links.rs @@ -1,6 +1,7 @@ use crate::constants::{BASE64, URI_COMPONENT_ENCODE_SET}; use crate::deep_links::StreamDeepLinks; use crate::types::addon::{ResourcePath, ResourceRequest}; +use crate::types::profile::Settings; use crate::types::resource::{Stream, StreamSource}; use base64::Engine; use percent_encoding::utf8_percent_encode; @@ -27,7 +28,8 @@ fn stream_deep_links_magnet() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(sdl.player, "stremio:///player/eAEBRgC5%2F3sidXJsIjoibWFnbmV0Oj94dD11cm46YnRpaDpkZDgyNTVlY2RjN2NhNTVmYjBiYmY4MTMyM2Q4NzA2MmRiMWY2ZDFjIn0%2BMhZF".to_string()); assert_eq!(sdl.external_player.href, Some(MAGNET_STR_URL.to_owned())); assert_eq!(sdl.external_player.file_name, None); @@ -46,7 +48,8 @@ fn stream_deep_links_http() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!( sdl.player, "stremio:///player/eAEBIQDe%2F3sidXJsIjoiaHR0cDovL2RvbWFpbi5yb290L3BhdGgifcEEC6w%3D" @@ -80,7 +83,8 @@ fn stream_deep_links_torrent() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(sdl.player, "stremio:///player/eAEBdwCI%2F3siaW5mb0hhc2giOiJkZDgyNTVlY2RjN2NhNTVmYjBiYmY4MTMyM2Q4NzA2MmRiMWY2ZDFjIiwiZmlsZUlkeCI6MCwiYW5ub3VuY2UiOlsiaHR0cDovL2J0MS5hcmNoaXZlLm9yZzo2OTY5L2Fubm91bmNlIl19ndAlsw%3D%3D".to_string()); assert_eq!( sdl.external_player.href, @@ -123,7 +127,8 @@ fn stream_deep_links_external() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(sdl.player, "stremio:///player/eAEBKQDW%2F3siZXh0ZXJuYWxVcmwiOiJodHRwOi8vZG9tYWluLnJvb3QvcGF0aCJ9OoEO7w%3D%3D".to_string()); assert_eq!(sdl.external_player.href, Some(HTTP_STR_URL.to_owned())); assert_eq!(sdl.external_player.file_name, None); @@ -142,7 +147,8 @@ fn stream_deep_links_youtube() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!( sdl.player, "stremio:///player/eAEBFgDp%2F3sieXRJZCI6ImFxei1LRS1icEtRIn1RRQb5".to_string() @@ -176,7 +182,8 @@ fn stream_deep_links_player_frame() { behavior_hints: Default::default(), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let sdl = StreamDeepLinks::try_from((&stream, &streaming_server_url, &settings)).unwrap(); assert_eq!(sdl.player, "stremio:///player/eAEBLADT%2F3sicGxheWVyRnJhbWVVcmwiOiJodHRwOi8vZG9tYWluLnJvb3QvcGF0aCJ9abUQBA%3D%3D".to_string()); assert_eq!(sdl.external_player.href, Some(HTTP_STR_URL.to_owned())); assert_eq!(sdl.external_player.file_name, None); @@ -204,11 +211,13 @@ fn stream_deep_links_requests() { }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); + let settings = Settings::default(); let sdl = StreamDeepLinks::try_from(( &stream, &stream_request, &meta_request, &streaming_server_url, + &settings, )) .unwrap(); assert_eq!(sdl.player, format!( diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index 9ebc34ad7..6307e913a 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -1,6 +1,7 @@ use crate::constants::BASE64; -use crate::deep_links::{ExternalPlayerLink, OpenPlayerLink, VideoDeepLinks}; +use crate::deep_links::{ExternalPlayerLink, VideoDeepLinks}; use crate::types::addon::{ResourcePath, ResourceRequest}; +use crate::types::profile::Settings; use crate::types::resource::Video; use base64::Engine; use std::convert::TryFrom; @@ -27,7 +28,9 @@ fn video_deep_links() { path: ResourcePath::without_extra("meta", "movie", format!("yt_id:{YT_ID}").as_str()), }; let streaming_server_url = Some(Url::parse(STREAMING_SERVER_URL).unwrap()); - let vdl = VideoDeepLinks::try_from((&video, &request, &streaming_server_url)).unwrap(); + let settings = Settings::default(); + let vdl = + VideoDeepLinks::try_from((&video, &request, &streaming_server_url, &settings)).unwrap(); assert_eq!( vdl.meta_details_streams, format!( @@ -52,16 +55,6 @@ fn video_deep_links() { download: Some(format!("https://youtube.com/watch?v={}", YT_ID)), streaming: Some(format!("{}yt/{}", STREAMING_SERVER_URL, YT_ID)), file_name: Some("playlist.m3u".to_string()), - vlc: Some(OpenPlayerLink { - ios: format!( - "vlc-x-callback://x-callback-url/stream?url={}yt/{}", - STREAMING_SERVER_URL, YT_ID, - ), - android: format!( - "intent://{}yt/{}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", - STREAMING_SERVER_URL, YT_ID, - ), - }), ..Default::default() }) ); From 6abdecf84270ce49777bb70ad892381287faf83f Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 30 May 2023 15:16:19 +0200 Subject: [PATCH 29/61] refactor(unit_tests): remove unused var --- src/unit_tests/deep_links/video_deep_links.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index e7958536c..ee7e9d863 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -14,7 +14,6 @@ const YT_ID: &str = "aqz-KE-bpKQ"; #[test] fn video_deep_links() { - let http_regex = Regex::new(r"https?://").unwrap(); let video = Video { id: format!("yt_id:UCSMOQeBJ2RAnuFungnQOxLg:{YT_ID}"), title: "Big Buck Bunny".to_string(), From 06312763e20bcc9e07be4a9f402f43895d240cf1 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 16:42:00 +0300 Subject: [PATCH 30/61] Fix Android Intents for External Players --- src/deep_links/mod.rs | 12 ++++++++---- src/unit_tests/deep_links/video_deep_links.rs | 16 ++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index 2a4c53cef..9c11b4f17 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -55,7 +55,8 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { let href = m3u_uri.or_else(|| download.to_owned()); let choose = streaming.as_ref().map(|url| OpenPlayerLink { android: Some(format!( - "intent:{url}#Intent;type=video/any;scheme=https;end", + "{}#Intent;type=video/any;scheme=https;end", + http_regex.replace(url, "intent://"), )), ios: None, windows: None, @@ -69,7 +70,8 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { let vlc = streaming.as_ref().map(|url| OpenPlayerLink { ios: Some(format!("vlc-x-callback://x-callback-url/stream?url={url}")), android: Some(format!( - "intent:{url}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", + "{}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", + http_regex.replace(url, "intent://"), )), windows: None, macos: None, @@ -81,7 +83,8 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { }); let mxplayer = streaming.as_ref().map(|url| OpenPlayerLink { android: Some(format!( - "intent:{url}#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=https;end", + "{}#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=https;end", + http_regex.replace(url, "intent://"), )), ios: None, windows: None, @@ -94,7 +97,8 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { }); let justplayer = streaming.as_ref().map(|url| OpenPlayerLink { android: Some(format!( - "intent:{url}#Intent;package=com.brouken.player;type=video;scheme=https;end", + "{}#Intent;package=com.brouken.player;type=video;scheme=https;end", + http_regex.replace(url, "intent://"), )), ios: None, windows: None, diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index c219aa945..301fbda44 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -57,8 +57,8 @@ fn video_deep_links() { file_name: Some("playlist.m3u".to_string()), choose: Some(OpenPlayerLink { android: Some(format!( - "intent:{}#Intent;type=video/any;scheme=https;end", - streaming_server_yt, + "{}#Intent;type=video/any;scheme=https;end", + http_regex.replace(&streaming_server_yt, "intent://"), )), ios: None, windows: None, @@ -75,8 +75,8 @@ fn video_deep_links() { streaming_server_yt, )), android: Some(format!( - "intent:{}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", - streaming_server_yt, + "{}#Intent;package=org.videolan.vlc;type=video;scheme=https;end", + http_regex.replace(&streaming_server_yt, "intent://"), )), windows: None, macos: None, @@ -88,8 +88,8 @@ fn video_deep_links() { }), mxplayer: Some(OpenPlayerLink { android: Some(format!( - "intent:{}#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=https;end", - streaming_server_yt, + "{}#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=https;end", + http_regex.replace(&streaming_server_yt, "intent://"), )), ios: None, windows: None, @@ -102,8 +102,8 @@ fn video_deep_links() { }), justplayer: Some(OpenPlayerLink { android: Some(format!( - "intent:{}#Intent;package=com.brouken.player;type=video;scheme=https;end", - streaming_server_yt, + "{}#Intent;package=com.brouken.player;type=video;scheme=https;end", + http_regex.replace(&streaming_server_yt, "intent://"), )), ios: None, windows: None, From 6609a9aa0499fc190368b6e9d7ab331024e3d5b6 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 16:45:44 +0300 Subject: [PATCH 31/61] Lint --- src/unit_tests/deep_links/video_deep_links.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index 301fbda44..ef0ffcd7c 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -47,10 +47,7 @@ fn video_deep_links() { Some(ExternalPlayerLink { href: Some(format!( "data:application/octet-stream;charset=utf-8;base64,{}", - BASE64.encode(format!( - "#EXTM3U\n#EXTINF:0\n{}", - streaming_server_yt - )) + BASE64.encode(format!("#EXTM3U\n#EXTINF:0\n{}", streaming_server_yt)) )), download: Some(format!("https://youtube.com/watch?v={}", YT_ID)), streaming: Some(format!("{}yt/{}", STREAMING_SERVER_URL, YT_ID)), @@ -115,7 +112,10 @@ fn video_deep_links() { roku: None, }), outplayer: Some(OpenPlayerLink { - ios: Some(format!("{}", http_regex.replace(&streaming_server_yt, "outplayer://"))), + ios: Some(format!( + "{}", + http_regex.replace(&streaming_server_yt, "outplayer://") + )), android: None, windows: None, macos: None, @@ -126,7 +126,10 @@ fn video_deep_links() { roku: None, }), infuse: Some(OpenPlayerLink { - ios: Some(format!("{}", http_regex.replace(&streaming_server_yt, "infuse://"))), + ios: Some(format!( + "{}", + http_regex.replace(&streaming_server_yt, "infuse://") + )), android: None, windows: None, macos: None, From fa1567aba648c18dd28411509a6b0379d33e7e22 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 17:59:17 +0300 Subject: [PATCH 32/61] Update Infuse Intent --- src/deep_links/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index 9c11b4f17..c83289d4b 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -121,7 +121,7 @@ impl From<(&Stream, &Option)> for ExternalPlayerLink { roku: None, }); let infuse = streaming.as_ref().map(|url| OpenPlayerLink { - ios: Some(format!("{}", http_regex.replace(url, "infuse://"))), + ios: Some(format!("infuse://x-callback-url/play?url={url}")), android: None, windows: None, macos: None, From 4386a8a02af6eaea82fcf93fababffc2a246ebe5 Mon Sep 17 00:00:00 2001 From: Alexandru Branza Date: Tue, 30 May 2023 18:00:09 +0300 Subject: [PATCH 33/61] Update Infuse Test --- src/unit_tests/deep_links/video_deep_links.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/unit_tests/deep_links/video_deep_links.rs b/src/unit_tests/deep_links/video_deep_links.rs index ef0ffcd7c..88d91fbe5 100644 --- a/src/unit_tests/deep_links/video_deep_links.rs +++ b/src/unit_tests/deep_links/video_deep_links.rs @@ -127,8 +127,8 @@ fn video_deep_links() { }), infuse: Some(OpenPlayerLink { ios: Some(format!( - "{}", - http_regex.replace(&streaming_server_yt, "infuse://") + "infuse://x-callback-url/play?url={}", + streaming_server_yt, )), android: None, windows: None, From 6d648be0d370062702d0cc9f35eccc82ee262c91 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 30 May 2023 17:15:49 +0200 Subject: [PATCH 34/61] chore: update regex --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e54e14389..5fd4444e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ itertools = "0.10" magnet-url = "2.0" hex = "0.4" anyhow = "1.0" -regex = "1.6.*" +regex = "1.8" # Tracing tracing = "0.1" From ee95fb4a9d4fe5e4bf4414d40bc51d86cd5e65c3 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Wed, 31 May 2023 10:13:16 +0300 Subject: [PATCH 35/61] fix: deeplinks infuse Signed-off-by: Lachezar Lechev --- src/deep_links/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deep_links/mod.rs b/src/deep_links/mod.rs index a5716c36d..0fa2212d0 100644 --- a/src/deep_links/mod.rs +++ b/src/deep_links/mod.rs @@ -86,7 +86,7 @@ impl From<(&Stream, &Option, &Settings)> for ExternalPlayerLink { ..Default::default() }), "infuse" => Some(OpenPlayerLink { - ios: Some(format!("{}", http_regex.replace(url, "infuse://"))), + ios: Some(format!("infuse://x-callback-url/play?url={url}")), ..Default::default() }), _ => None, From c121af884bbc7cd477552f8da354b9233d3888cc Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Mon, 5 Jun 2023 13:32:25 +0300 Subject: [PATCH 36/61] chore: update debug to trace on auth responses Signed-off-by: Lachezar Lechev --- src/models/ctx/ctx.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/models/ctx/ctx.rs b/src/models/ctx/ctx.rs index 1cb305510..d4cabd893 100644 --- a/src/models/ctx/ctx.rs +++ b/src/models/ctx/ctx.rs @@ -19,7 +19,7 @@ use percent_encoding::utf8_percent_encode; use serde::Serialize; use url::Url; -use tracing::{debug, event, Level}; +use tracing::{event, trace, Level}; #[derive(PartialEq, Eq, Serialize, Clone, Debug)] pub enum CtxStatus { @@ -181,7 +181,7 @@ fn authenticate(auth_request: &AuthRequest) -> Effect { E::flush_analytics() .then(move |_| { fetch_api::(&auth_api) - .inspect(move |result| debug!(?result, ?auth_api, "Auth request")) + .inspect(move |result| trace!(?result, ?auth_api, "Auth request")) }) .map_err(CtxError::from) .and_then(|result| match result { @@ -197,7 +197,7 @@ fn authenticate(auth_request: &AuthRequest) -> Effect { }; fetch_api::(&request) .inspect(move |result| { - debug!(?result, ?request, "Get user's Addon Collection request") + trace!(?result, ?request, "Get user's Addon Collection request") }) .map_err(CtxError::from) .and_then(|result| match result { @@ -219,7 +219,7 @@ fn authenticate(auth_request: &AuthRequest) -> Effect { fetch_api::(&request) .inspect(move |result| { - debug!(?result, ?request, "Get user's Addon Collection request") + trace!(?result, ?request, "Get user's Addon Collection request") }) .map_err(CtxError::from) .and_then(|result| match result { @@ -251,7 +251,7 @@ fn delete_session(auth_key: &AuthKey) -> Effect { E::flush_analytics() .then(|_| { fetch_api::(&request) - .inspect(move |result| debug!(?result, ?request, "Logout request")) + .inspect(move |result| trace!(?result, ?request, "Logout request")) }) .map_err(CtxError::from) .and_then(|result| match result { From eda7b73e53e8ddb11d275bbfc3ea1cb3cdf48316 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Mon, 5 Jun 2023 21:46:49 -0700 Subject: [PATCH 37/61] impl StreamsBucket --- src/constants.rs | 1 + src/models/ctx/ctx.rs | 20 ++++++-- src/models/ctx/mod.rs | 3 ++ src/models/ctx/update_streams.rs | 75 +++++++++++++++++++++++++++++ src/models/player.rs | 31 ++++++++++++ src/runtime/msg/event.rs | 3 ++ src/runtime/msg/internal.rs | 5 ++ src/types/mod.rs | 1 + src/types/streams/mod.rs | 5 ++ src/types/streams/streams_bucket.rs | 25 ++++++++++ src/types/streams/streams_item.rs | 15 ++++++ 11 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 src/models/ctx/update_streams.rs create mode 100644 src/types/streams/mod.rs create mode 100644 src/types/streams/streams_bucket.rs create mode 100644 src/types/streams/streams_item.rs diff --git a/src/constants.rs b/src/constants.rs index 1be6e7d5c..4af6bbb05 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -8,6 +8,7 @@ pub const SCHEMA_VERSION_STORAGE_KEY: &str = "schema_version"; 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 LIBRARY_COLLECTION_NAME: &str = "libraryItem"; pub const SEARCH_EXTRA_NAME: &str = "search"; pub const META_RESOURCE_NAME: &str = "meta"; diff --git a/src/models/ctx/ctx.rs b/src/models/ctx/ctx.rs index d4cabd893..b84d445ff 100644 --- a/src/models/ctx/ctx.rs +++ b/src/models/ctx/ctx.rs @@ -2,7 +2,7 @@ use crate::constants::{LIBRARY_COLLECTION_NAME, URI_COMPONENT_ENCODE_SET}; use crate::models::common::{ descriptor_update, eq_update, DescriptorAction, DescriptorLoadable, Loadable, }; -use crate::models::ctx::{update_library, update_profile, CtxError, OtherError}; +use crate::models::ctx::{update_library, update_profile, update_streams, CtxError, OtherError}; use crate::runtime::msg::{Action, ActionCtx, Event, Internal, Msg}; use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt, Update}; use crate::types::api::{ @@ -11,6 +11,7 @@ use crate::types::api::{ }; use crate::types::library::LibraryBucket; use crate::types::profile::{Auth, AuthKey, Profile}; +use crate::types::streams::StreamsBucket; use derivative::Derivative; use enclose::enclose; @@ -31,12 +32,13 @@ pub enum CtxStatus { #[derivative(Default)] pub struct Ctx { pub profile: Profile, - // TODO StreamsBucket // TODO SubtitlesBucket // TODO SearchesBucket #[serde(skip)] pub library: LibraryBucket, #[serde(skip)] + pub streams: StreamsBucket, + #[serde(skip)] #[derivative(Default(value = "CtxStatus::Ready"))] pub status: CtxStatus, #[serde(skip)] @@ -44,10 +46,11 @@ pub struct Ctx { } impl Ctx { - pub fn new(profile: Profile, library: LibraryBucket) -> Self { + pub fn new(profile: Profile, library: LibraryBucket, streams: StreamsBucket) -> Self { Self { profile, library, + streams, ..Self::default() } } @@ -72,6 +75,7 @@ impl Update for Ctx { let profile_effects = update_profile::(&mut self.profile, &self.status, msg); 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 trakt_addon_effects = eq_update(&mut self.trakt_addon, None); self.status = CtxStatus::Ready; Effects::msg(Msg::Event(Event::UserLoggedOut { uid })) @@ -79,6 +83,7 @@ impl Update for Ctx { .join(session_effects) .join(profile_effects) .join(library_effects) + .join(streams_effects) .join(trakt_addon_effects) } Msg::Action(Action::Ctx(ActionCtx::InstallTraktAddon)) => { @@ -141,6 +146,7 @@ impl Update for Ctx { let profile_effects = update_profile::(&mut self.profile, &self.status, msg); 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 ctx_effects = match &self.status { CtxStatus::Loading(loading_auth_request) if loading_auth_request == auth_request => @@ -162,13 +168,17 @@ impl Update for Ctx { } _ => Effects::none().unchanged(), }; - profile_effects.join(library_effects).join(ctx_effects) + profile_effects + .join(library_effects) + .join(streams_effects) + .join(ctx_effects) } _ => { let profile_effects = update_profile::(&mut self.profile, &self.status, msg); let library_effects = update_library::(&mut self.library, &self.profile, &self.status, msg); - profile_effects.join(library_effects) + let streams_effects = update_streams::(&mut self.streams, &self.status, msg); + profile_effects.join(library_effects).join(streams_effects) } } } diff --git a/src/models/ctx/mod.rs b/src/models/ctx/mod.rs index 7dc183757..6f915ed54 100644 --- a/src/models/ctx/mod.rs +++ b/src/models/ctx/mod.rs @@ -4,6 +4,9 @@ use update_library::*; mod update_profile; use update_profile::*; +mod update_streams; +use update_streams::*; + mod error; pub use error::*; diff --git a/src/models/ctx/update_streams.rs b/src/models/ctx/update_streams.rs new file mode 100644 index 000000000..3ef5e2420 --- /dev/null +++ b/src/models/ctx/update_streams.rs @@ -0,0 +1,75 @@ +use enclose::enclose; +use futures::FutureExt; + +use crate::constants::STREAMS_STORAGE_KEY; +use crate::models::ctx::{CtxError, CtxStatus}; +use crate::runtime::msg::{Action, ActionCtx, Event, Internal, Msg}; +use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt}; +use crate::types::streams::{StreamsBucket, StreamsItem}; + +pub fn update_streams( + streams: &mut StreamsBucket, + status: &CtxStatus, + msg: &Msg, +) -> Effects { + match msg { + Msg::Action(Action::Ctx(ActionCtx::Logout)) | Msg::Internal(Internal::Logout) => { + let next_streams = StreamsBucket::default(); + if *streams != next_streams { + *streams = next_streams; + Effects::msg(Msg::Internal(Internal::StreamsChanged(false))) + } else { + Effects::none().unchanged() + } + } + Msg::Internal(Internal::StreamLoaded( + Some(meta_item_id), + Some(video_id), + Some(transport_url), + stream, + )) => { + let streams_item = StreamsItem { + stream: stream.to_owned(), + transport_url: transport_url.to_owned(), + mtime: E::now(), + }; + streams + .items + .insert((meta_item_id.to_owned(), video_id.to_owned()), streams_item); + Effects::msg(Msg::Internal(Internal::StreamsChanged(false))) + } + Msg::Internal(Internal::StreamsChanged(persisted)) if !persisted => { + Effects::one(push_streams_to_storage::(streams)).unchanged() + } + 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_streams = StreamsBucket::new(Some(auth.user.id.to_owned())); + if *streams != next_streams { + *streams = next_streams; + Effects::msg(Msg::Internal(Internal::StreamsChanged(false))) + } else { + Effects::none().unchanged() + } + } + _ => Effects::none().unchanged(), + }, + _ => Effects::none().unchanged(), + } +} + +fn push_streams_to_storage(streams: &StreamsBucket) -> Effect { + EffectFuture::Sequential( + E::set_storage(STREAMS_STORAGE_KEY, Some(&streams)) + .map(enclose!((streams.uid => uid) move |result| match result { + Ok(_) => Msg::Event(Event::StreamsPushedToStorage { uid }), + Err(error) => Msg::Event(Event::Error { + error: CtxError::from(error), + source: Box::new(Event::StreamsPushedToStorage { uid }), + }) + })) + .boxed_env(), + ) + .into() +} diff --git a/src/models/player.rs b/src/models/player.rs index 4ad5ac5c8..1b35d6f04 100644 --- a/src/models/player.rs +++ b/src/models/player.rs @@ -116,6 +116,36 @@ impl UpdateWithCtx for Player { } else { Effects::none().unchanged() }; + let update_streams_effects = if self.selected.as_ref().map(|selected| { + ( + &selected.stream, + &selected.stream_request, + &selected.meta_request, + ) + }) != Some(( + &selected.stream, + &selected.stream_request, + &selected.meta_request, + )) { + Effects::msg(Msg::Internal(Internal::StreamLoaded( + selected + .meta_request + .as_ref() + .map(|meta_request| meta_request.path.id.to_owned()), + selected + .stream_request + .as_ref() + .map(|stream_request| stream_request.path.id.to_owned()), + selected + .stream_request + .as_ref() + .map(|stream_request| stream_request.base.to_owned()), + selected.stream.to_owned(), + ))) + .unchanged() + } else { + Effects::none().unchanged() + }; let selected_effects = eq_update(&mut self.selected, Some(*selected.to_owned())); let meta_item_effects = match &selected.meta_request { Some(meta_request) => match &mut self.meta_item { @@ -222,6 +252,7 @@ impl UpdateWithCtx for Player { self.ended = false; self.paused = None; switch_to_next_video_effects + .join(update_streams_effects) .join(selected_effects) .join(meta_item_effects) .join(subtitles_effects) diff --git a/src/runtime/msg/event.rs b/src/runtime/msg/event.rs index 114676aed..6b313addc 100644 --- a/src/runtime/msg/event.rs +++ b/src/runtime/msg/event.rs @@ -36,6 +36,9 @@ pub enum Event { LibraryItemsPushedToStorage { ids: Vec, }, + StreamsPushedToStorage { + uid: UID, + }, UserPulledFromAPI { uid: UID, }, diff --git a/src/runtime/msg/internal.rs b/src/runtime/msg/internal.rs index b3395b9aa..1593d38e5 100644 --- a/src/runtime/msg/internal.rs +++ b/src/runtime/msg/internal.rs @@ -11,6 +11,7 @@ use crate::types::api::{ }; use crate::types::library::{LibraryBucket, LibraryItem}; use crate::types::profile::{Auth, AuthKey, Profile, User}; +use crate::types::resource::Stream; use crate::types::streaming_server::Statistics; use url::Url; @@ -43,12 +44,16 @@ pub enum Internal { Logout, /// Dispatched when addons needs to be installed. InstallAddon(Descriptor), + /// Dispatched when a new stream is loaded into the Player. + StreamLoaded(Option, Option, Option, Stream), /// 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. ProfileChanged, /// Dispatched when library changes with a flag if its already persisted. LibraryChanged(bool), + /// Dispatched when streams bucket changes with a flag if its already persisted. + StreamsChanged(bool), /// Result for loading link code. LinkCodeResult(Result), /// Result for loading link data. diff --git a/src/types/mod.rs b/src/types/mod.rs index 95ae5a8f2..a69909643 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -4,6 +4,7 @@ pub mod library; pub mod profile; pub mod resource; pub mod streaming_server; +pub mod streams; mod query_params_encode; pub use query_params_encode::*; diff --git a/src/types/streams/mod.rs b/src/types/streams/mod.rs new file mode 100644 index 000000000..0eae18df0 --- /dev/null +++ b/src/types/streams/mod.rs @@ -0,0 +1,5 @@ +mod streams_item; +pub use streams_item::*; + +mod streams_bucket; +pub use streams_bucket::*; diff --git a/src/types/streams/streams_bucket.rs b/src/types/streams/streams_bucket.rs new file mode 100644 index 000000000..2579893e7 --- /dev/null +++ b/src/types/streams/streams_bucket.rs @@ -0,0 +1,25 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +use crate::types::profile::UID; +use crate::types::streams::StreamsItem; + +#[serde_as] +#[derive(Default, Clone, PartialEq, Serialize, Deserialize)] +#[cfg_attr(debug_assertions, derive(Debug))] +pub struct StreamsBucket { + pub uid: UID, + #[serde_as(as = "Vec<(_, _)>")] + pub items: HashMap<(String, String), StreamsItem>, +} + +impl StreamsBucket { + pub fn new(uid: UID) -> Self { + StreamsBucket { + uid, + items: HashMap::new(), + } + } +} diff --git a/src/types/streams/streams_item.rs b/src/types/streams/streams_item.rs new file mode 100644 index 000000000..c293696ab --- /dev/null +++ b/src/types/streams/streams_item.rs @@ -0,0 +1,15 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use url::Url; + +use crate::types::resource::Stream; + +#[serde_as] +#[derive(Clone, PartialEq, Serialize, Deserialize)] +#[cfg_attr(debug_assertions, derive(Debug))] +pub struct StreamsItem { + pub stream: Stream, + pub transport_url: Url, + pub mtime: DateTime, +} From 7108c72856c9a3a8a0bc91391d9844e426646e46 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Mon, 12 Jun 2023 15:21:22 -0700 Subject: [PATCH 38/61] override meta details selected based on loaded meta --- src/models/meta_details.rs | 61 +++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs index 11679b46d..467fba40e 100644 --- a/src/models/meta_details.rs +++ b/src/models/meta_details.rs @@ -112,6 +112,13 @@ impl UpdateWithCtx for MetaDetails { &mut self.meta_items, ResourcesAction::ResourceRequestResult { request, result }, ); + let selected_effects: Effects = + selected_override_update(&mut self.selected, &self.meta_items); + let streams_effects = if selected_effects.has_changed { + streams_update::(&mut self.streams, &self.selected, &ctx.profile) + } else { + Effects::default() + }; let meta_streams_effects = meta_streams_update(&mut self.meta_streams, &self.selected, &self.meta_items); let library_item_effects = library_item_update::( @@ -122,8 +129,10 @@ impl UpdateWithCtx for MetaDetails { ); let watched_effects = watched_update(&mut self.watched, &self.meta_items, &self.library_item); - meta_items_effects + selected_effects + .join(meta_items_effects) .join(meta_streams_effects) + .join(streams_effects) .join(library_item_effects) .join(watched_effects) } @@ -189,6 +198,56 @@ fn library_item_sync(library_item: &Option, profile: &Profile) -> E } } +fn selected_override_update( + selected: &mut Option, + meta_items: &Vec>, +) -> Effects { + let meta_path = if let Some(Selected { + meta_path, + stream_path: None, + }) = &selected + { + meta_path + } else { + return Effects::default(); + }; + let viable_meta_item = if let Some(viable_meta_item) = meta_items + .iter() + .find_map(|meta_item| match &meta_item.content { + Some(Loadable::Ready(meta_item)) => Some(Some(meta_item)), + Some(Loadable::Loading) => Some(None), + _ => None, + }) + .flatten() + { + viable_meta_item + } else { + return Effects::default(); + }; + let video_id = match ( + viable_meta_item.videos.len(), + viable_meta_item.preview.r#type.as_str(), + &viable_meta_item.preview.behavior_hints.default_video_id, + ) { + (1, _, _) => viable_meta_item.videos.first().unwrap().id.to_owned(), + (0, _, Some(default_video_id)) => default_video_id.to_owned(), + (0, "movie", _) => viable_meta_item.preview.id.to_owned(), + _ => return Effects::default(), + }; + eq_update( + selected, + Some(Selected { + meta_path: meta_path.to_owned(), + stream_path: Some(ResourcePath { + resource: STREAM_RESOURCE_NAME.to_owned(), + r#type: meta_path.r#type.to_owned(), + id: video_id, + extra: vec![], + }), + }), + ) +} + fn meta_items_update( meta_items: &mut Vec>, selected: &Option, From 665def393dae15f1f4447a19a153db71968fa1f2 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Tue, 13 Jun 2023 00:05:46 -0700 Subject: [PATCH 39/61] remove not needed type --- src/models/meta_details.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs index 467fba40e..6d3d8864d 100644 --- a/src/models/meta_details.rs +++ b/src/models/meta_details.rs @@ -112,7 +112,7 @@ impl UpdateWithCtx for MetaDetails { &mut self.meta_items, ResourcesAction::ResourceRequestResult { request, result }, ); - let selected_effects: Effects = + let selected_effects = selected_override_update(&mut self.selected, &self.meta_items); let streams_effects = if selected_effects.has_changed { streams_update::(&mut self.streams, &self.selected, &ctx.profile) From f946b8bd48169ffca00601e0e27866f589ffd93d Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Tue, 13 Jun 2023 14:06:38 -0700 Subject: [PATCH 40/61] change the conditions to override meta details selected --- src/models/meta_details.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs index 6d3d8864d..bdc5ffba0 100644 --- a/src/models/meta_details.rs +++ b/src/models/meta_details.rs @@ -23,7 +23,7 @@ pub struct Selected { pub stream_path: Option, } -#[derive(Default, Serialize, Debug)] +#[derive(Default, Serialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct MetaDetails { pub selected: Option, @@ -42,6 +42,8 @@ impl UpdateWithCtx for MetaDetails { let selected_effects = eq_update(&mut self.selected, Some(selected.to_owned())); let meta_items_effects = meta_items_update::(&mut self.meta_items, &self.selected, &ctx.profile); + let selected_override_effects = + selected_override_update(&mut self.selected, &self.meta_items); let meta_streams_effects = meta_streams_update(&mut self.meta_streams, &self.selected, &self.meta_items); let streams_effects = @@ -57,6 +59,7 @@ impl UpdateWithCtx for MetaDetails { let libraty_item_sync_effects = library_item_sync(&self.library_item, &ctx.profile); libraty_item_sync_effects .join(selected_effects) + .join(selected_override_effects) .join(meta_items_effects) .join(meta_streams_effects) .join(streams_effects) @@ -112,9 +115,9 @@ impl UpdateWithCtx for MetaDetails { &mut self.meta_items, ResourcesAction::ResourceRequestResult { request, result }, ); - let selected_effects = + let selected_override_effects = selected_override_update(&mut self.selected, &self.meta_items); - let streams_effects = if selected_effects.has_changed { + let streams_effects = if selected_override_effects.has_changed { streams_update::(&mut self.streams, &self.selected, &ctx.profile) } else { Effects::default() @@ -129,7 +132,7 @@ impl UpdateWithCtx for MetaDetails { ); let watched_effects = watched_update(&mut self.watched, &self.meta_items, &self.library_item); - selected_effects + selected_override_effects .join(meta_items_effects) .join(meta_streams_effects) .join(streams_effects) @@ -226,12 +229,11 @@ fn selected_override_update( }; let video_id = match ( viable_meta_item.videos.len(), - viable_meta_item.preview.r#type.as_str(), &viable_meta_item.preview.behavior_hints.default_video_id, ) { - (1, _, _) => viable_meta_item.videos.first().unwrap().id.to_owned(), - (0, _, Some(default_video_id)) => default_video_id.to_owned(), - (0, "movie", _) => viable_meta_item.preview.id.to_owned(), + (_, Some(default_video_id)) => default_video_id.to_owned(), + (1, _) => viable_meta_item.videos.first().unwrap().id.to_owned(), + (0, None) => viable_meta_item.preview.id.to_owned(), _ => return Effects::default(), }; eq_update( From 3cc2a73f6c2a7c30af80abd7e3740713a8397402 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Tue, 13 Jun 2023 14:08:16 -0700 Subject: [PATCH 41/61] unit tests added --- src/models/link.rs | 2 +- src/runtime/runtime.rs | 5 +- src/runtime/update.rs | 2 +- src/types/resource/meta_item.rs | 1 + .../catalog_with_filters/load_action.rs | 8 +- src/unit_tests/ctx/add_to_library.rs | 4 +- src/unit_tests/ctx/authenticate.rs | 6 +- src/unit_tests/ctx/install_addon.rs | 8 +- src/unit_tests/ctx/logout.rs | 2 +- src/unit_tests/ctx/pull_addons_from_api.rs | 4 +- src/unit_tests/ctx/push_addons_to_api.rs | 4 +- src/unit_tests/ctx/remove_from_library.rs | 4 +- src/unit_tests/ctx/rewind_library_item.rs | 4 +- src/unit_tests/ctx/sync_library_with_api.rs | 6 +- src/unit_tests/ctx/uninstall_addon.rs | 8 +- src/unit_tests/ctx/update_settings.rs | 4 +- src/unit_tests/ctx/upgrade_addon.rs | 4 +- src/unit_tests/data_export.rs | 4 +- src/unit_tests/env.rs | 24 +- src/unit_tests/link.rs | 2 +- src/unit_tests/meta_details/mod.rs | 1 + .../meta_details/override_selected.rs | 328 ++++++++++++++++++ src/unit_tests/mod.rs | 6 +- 23 files changed, 386 insertions(+), 55 deletions(-) create mode 100644 src/unit_tests/meta_details/mod.rs create mode 100644 src/unit_tests/meta_details/override_selected.rs diff --git a/src/models/link.rs b/src/models/link.rs index fdbcea12a..e723e4f46 100644 --- a/src/models/link.rs +++ b/src/models/link.rs @@ -31,7 +31,7 @@ impl fmt::Display for LinkError { } } -#[derive(Derivative, Serialize, Debug)] +#[derive(Derivative, Serialize, Clone, Debug)] #[derivative(Default(bound = ""))] #[serde(rename_all = "camelCase")] pub struct Link { diff --git a/src/runtime/runtime.rs b/src/runtime/runtime.rs index 62354b23a..1900c73d4 100644 --- a/src/runtime/runtime.rs +++ b/src/runtime/runtime.rs @@ -13,7 +13,7 @@ use std::sync::{Arc, LockResult, RwLock, RwLockReadGuard}; #[derive(Serialize, Debug, PartialEq)] #[serde(tag = "name", content = "args")] pub enum RuntimeEvent> { - NewState(Vec), + NewState(Vec, M), CoreEvent(Event), } @@ -78,7 +78,8 @@ where } fn handle_effects(&self, effects: Vec, fields: Vec) { if !fields.is_empty() { - self.emit(RuntimeEvent::::NewState(fields)); + let model = self.model.read().expect("model read failed"); + self.emit(RuntimeEvent::::NewState(fields, model.to_owned())); }; effects .into_iter() diff --git a/src/runtime/update.rs b/src/runtime/update.rs index 1ceabe8cc..998009c29 100644 --- a/src/runtime/update.rs +++ b/src/runtime/update.rs @@ -5,7 +5,7 @@ use crate::runtime::{Effect, Effects, Env}; use core::fmt::Debug; use serde::{Deserialize, Serialize}; -pub trait Model { +pub trait Model: Clone { #[cfg(not(debug_assertions))] type Field: Send + Sync + Serialize + for<'de> Deserialize<'de>; #[cfg(debug_assertions)] diff --git a/src/types/resource/meta_item.rs b/src/types/resource/meta_item.rs index 6c8e0b90d..bb42d08f0 100644 --- a/src/types/resource/meta_item.rs +++ b/src/types/resource/meta_item.rs @@ -218,6 +218,7 @@ pub struct SeriesInfo { #[serde_as] #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +#[cfg_attr(test, derive(Default))] #[serde(rename_all = "camelCase")] pub struct Video { pub id: String, diff --git a/src/unit_tests/catalog_with_filters/load_action.rs b/src/unit_tests/catalog_with_filters/load_action.rs index 3bcc54fce..c9ba5dd74 100644 --- a/src/unit_tests/catalog_with_filters/load_action.rs +++ b/src/unit_tests/catalog_with_filters/load_action.rs @@ -65,13 +65,13 @@ fn default_catalog() { events[0] .downcast_ref::>() .unwrap(), - RuntimeEvent::NewState(fields) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::Discover + RuntimeEvent::NewState(fields, _) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::Discover ); assert_matches!( events[1] .downcast_ref::>() .unwrap(), - RuntimeEvent::NewState(fields) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::Discover + RuntimeEvent::NewState(fields, _) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::Discover ); let states = STATES.read().unwrap(); let states = states @@ -173,13 +173,13 @@ fn search_catalog() { events[0] .downcast_ref::>() .unwrap(), - RuntimeEvent::NewState(fields) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::Discover + RuntimeEvent::NewState(fields, _) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::Discover ); assert_matches!( events[1] .downcast_ref::>() .unwrap(), - RuntimeEvent::NewState(fields) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::Discover + RuntimeEvent::NewState(fields, _) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::Discover ); let states = STATES.read().unwrap(); let states = states diff --git a/src/unit_tests/ctx/add_to_library.rs b/src/unit_tests/ctx/add_to_library.rs index 0ee0551da..bfeae6157 100644 --- a/src/unit_tests/ctx/add_to_library.rs +++ b/src/unit_tests/ctx/add_to_library.rs @@ -18,7 +18,7 @@ use url::Url; #[test] fn actionctx_addtolibrary() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -159,7 +159,7 @@ fn actionctx_addtolibrary() { #[test] fn actionctx_addtolibrary_already_added() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/ctx/authenticate.rs b/src/unit_tests/ctx/authenticate.rs index 9a5407caa..e1837d32d 100644 --- a/src/unit_tests/ctx/authenticate.rs +++ b/src/unit_tests/ctx/authenticate.rs @@ -19,7 +19,7 @@ use stremio_derive::Model; #[test] fn actionctx_authenticate_login() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -218,7 +218,7 @@ fn actionctx_authenticate_login() { #[test] fn actionctx_authenticate_login_with_token() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -416,7 +416,7 @@ fn actionctx_authenticate_login_with_token() { #[test] fn actionctx_authenticate_register() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/ctx/install_addon.rs b/src/unit_tests/ctx/install_addon.rs index d7cb0b66e..90f9d874d 100644 --- a/src/unit_tests/ctx/install_addon.rs +++ b/src/unit_tests/ctx/install_addon.rs @@ -17,7 +17,7 @@ use url::Url; #[test] fn actionctx_installaddon_install() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -84,7 +84,7 @@ fn actionctx_installaddon_install() { #[test] fn actionctx_installaddon_install_with_user() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -198,7 +198,7 @@ fn actionctx_installaddon_install_with_user() { #[test] fn actionctx_installaddon_update() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -306,7 +306,7 @@ fn actionctx_installaddon_update() { #[test] fn actionctx_installaddon_already_installed() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/ctx/logout.rs b/src/unit_tests/ctx/logout.rs index bc37acec9..ad6400641 100644 --- a/src/unit_tests/ctx/logout.rs +++ b/src/unit_tests/ctx/logout.rs @@ -15,7 +15,7 @@ use stremio_derive::Model; #[test] fn actionctx_logout() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/ctx/pull_addons_from_api.rs b/src/unit_tests/ctx/pull_addons_from_api.rs index bc962fa9a..df63fcd9f 100644 --- a/src/unit_tests/ctx/pull_addons_from_api.rs +++ b/src/unit_tests/ctx/pull_addons_from_api.rs @@ -16,7 +16,7 @@ use url::Url; #[test] fn actionctx_pulladdonsfromapi() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -73,7 +73,7 @@ fn actionctx_pulladdonsfromapi() { #[test] fn actionctx_pulladdonsfromapi_with_user() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/ctx/push_addons_to_api.rs b/src/unit_tests/ctx/push_addons_to_api.rs index 4bbed4895..cd65a05cc 100644 --- a/src/unit_tests/ctx/push_addons_to_api.rs +++ b/src/unit_tests/ctx/push_addons_to_api.rs @@ -14,7 +14,7 @@ use url::Url; #[test] fn actionctx_pushaddonstoapi() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -65,7 +65,7 @@ fn actionctx_pushaddonstoapi() { #[test] fn actionctx_pushaddonstoapi_with_user() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/ctx/remove_from_library.rs b/src/unit_tests/ctx/remove_from_library.rs index fbe2ac69a..c478b8d28 100644 --- a/src/unit_tests/ctx/remove_from_library.rs +++ b/src/unit_tests/ctx/remove_from_library.rs @@ -16,7 +16,7 @@ use stremio_derive::Model; #[test] fn actionctx_removefromlibrary() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -148,7 +148,7 @@ fn actionctx_removefromlibrary() { #[test] fn actionctx_removefromlibrary_not_added() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/ctx/rewind_library_item.rs b/src/unit_tests/ctx/rewind_library_item.rs index f8dc69790..7a71320ee 100644 --- a/src/unit_tests/ctx/rewind_library_item.rs +++ b/src/unit_tests/ctx/rewind_library_item.rs @@ -16,7 +16,7 @@ use stremio_derive::Model; #[test] fn actionctx_rewindlibraryitem() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -154,7 +154,7 @@ fn actionctx_rewindlibraryitem() { #[test] fn actionctx_rewindlibraryitem_not_added() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/ctx/sync_library_with_api.rs b/src/unit_tests/ctx/sync_library_with_api.rs index 2826cc475..038e257e4 100644 --- a/src/unit_tests/ctx/sync_library_with_api.rs +++ b/src/unit_tests/ctx/sync_library_with_api.rs @@ -19,7 +19,7 @@ use stremio_derive::Model; #[test] fn actionctx_synclibrarywithapi() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -40,7 +40,7 @@ fn actionctx_synclibrarywithapi() { #[test] fn actionctx_synclibrarywithapi_with_user() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -366,7 +366,7 @@ fn actionctx_synclibrarywithapi_with_user() { #[test] fn actionctx_synclibrarywithapi_with_user_empty_library() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/ctx/uninstall_addon.rs b/src/unit_tests/ctx/uninstall_addon.rs index 1c9ba8abd..463446c72 100644 --- a/src/unit_tests/ctx/uninstall_addon.rs +++ b/src/unit_tests/ctx/uninstall_addon.rs @@ -17,7 +17,7 @@ use url::Url; #[test] fn actionctx_uninstalladdon() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -91,7 +91,7 @@ fn actionctx_uninstalladdon() { #[test] fn actionctx_uninstalladdon_with_user() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -212,7 +212,7 @@ fn actionctx_uninstalladdon_with_user() { #[test] fn actionctx_uninstalladdon_protected() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -287,7 +287,7 @@ fn actionctx_uninstalladdon_protected() { #[test] fn actionctx_uninstalladdon_not_installed() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/ctx/update_settings.rs b/src/unit_tests/ctx/update_settings.rs index 633ac066b..5db31983a 100644 --- a/src/unit_tests/ctx/update_settings.rs +++ b/src/unit_tests/ctx/update_settings.rs @@ -8,7 +8,7 @@ use stremio_derive::Model; #[test] fn actionctx_updatesettings() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -49,7 +49,7 @@ fn actionctx_updatesettings() { #[test] fn actionctx_updatesettings_not_changed() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/ctx/upgrade_addon.rs b/src/unit_tests/ctx/upgrade_addon.rs index f80e49a2b..b38a89b3e 100644 --- a/src/unit_tests/ctx/upgrade_addon.rs +++ b/src/unit_tests/ctx/upgrade_addon.rs @@ -11,7 +11,7 @@ use url::Url; #[test] fn actionctx_addon_upgrade() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, @@ -118,7 +118,7 @@ fn actionctx_addon_upgrade() { #[test] fn actionctx_addon_upgrade_fail_due_to_different_url() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/data_export.rs b/src/unit_tests/data_export.rs index 8fd3d9519..91867023b 100644 --- a/src/unit_tests/data_export.rs +++ b/src/unit_tests/data_export.rs @@ -72,13 +72,13 @@ fn data_export_with_user() { events[0] .downcast_ref::>() .unwrap(), - RuntimeEvent::NewState(fields) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::DataExport + RuntimeEvent::NewState(fields, _) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::DataExport ); assert_matches!( events[1] .downcast_ref::>() .unwrap(), - RuntimeEvent::NewState(fields) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::DataExport + RuntimeEvent::NewState(fields, _) if fields.len() == 1 && *fields.first().unwrap() == TestModelField::DataExport ); let states = STATES.read().unwrap(); let states = states diff --git a/src/unit_tests/env.rs b/src/unit_tests/env.rs index 81f8b64cc..b458d5abd 100644 --- a/src/unit_tests/env.rs +++ b/src/unit_tests/env.rs @@ -77,17 +77,6 @@ impl TestEnv { runnable: F, ) { tokio_current_thread::block_on_all(future::lazy(|_| { - TestEnv::exec_concurrent(rx.for_each(enclose!((runtime) move |event| { - if let RuntimeEvent::NewState(_) = event { - let runtime = runtime.read().expect("runtime read failed"); - let state = runtime.model().expect("model read failed"); - let mut states = STATES.write().expect("states write failed"); - states.push(Box::new(state.to_owned()) as Box); - }; - let mut events = EVENTS.write().expect("events write failed"); - events.push(Box::new(event) as Box); - future::ready(()) - }))); { let runtime = runtime.read().expect("runtime read failed"); let state = runtime.model().expect("model read failed"); @@ -95,11 +84,22 @@ impl TestEnv { states.push(Box::new(state.to_owned()) as Box); } runnable(); + })); + tokio_current_thread::block_on_all(future::lazy(|_| { + TestEnv::exec_concurrent(rx.for_each(move |event| { + if let RuntimeEvent::NewState(_, state) = &event { + let mut states = STATES.write().expect("states write failed"); + states.push(Box::new(state.to_owned()) as Box); + }; + let mut events = EVENTS.write().expect("events write failed"); + events.push(Box::new(event) as Box); + future::ready(()) + })); TestEnv::exec_concurrent(enclose!((runtime) async move { let mut runtime = runtime.write().expect("runtime read failed"); runtime.close().await.unwrap(); })); - })) + })); } } diff --git a/src/unit_tests/link.rs b/src/unit_tests/link.rs index d5025046c..1ee74e00b 100644 --- a/src/unit_tests/link.rs +++ b/src/unit_tests/link.rs @@ -11,7 +11,7 @@ use stremio_derive::Model; #[test] fn create_link_code() { - #[derive(Model, Default)] + #[derive(Model, Clone, Default)] #[model(TestEnv)] struct TestModel { ctx: Ctx, diff --git a/src/unit_tests/meta_details/mod.rs b/src/unit_tests/meta_details/mod.rs new file mode 100644 index 000000000..dae54959f --- /dev/null +++ b/src/unit_tests/meta_details/mod.rs @@ -0,0 +1 @@ +mod override_selected; diff --git a/src/unit_tests/meta_details/override_selected.rs b/src/unit_tests/meta_details/override_selected.rs new file mode 100644 index 000000000..f10b06a0e --- /dev/null +++ b/src/unit_tests/meta_details/override_selected.rs @@ -0,0 +1,328 @@ +use crate::constants::{CINEMETA_URL, META_RESOURCE_NAME, OFFICIAL_ADDONS, STREAM_RESOURCE_NAME}; +use crate::models::ctx::Ctx; +use crate::models::meta_details::{MetaDetails, Selected}; +use crate::runtime::msg::{Action, ActionLoad}; +use crate::runtime::{EnvFutureExt, Runtime, RuntimeAction, TryEnvFuture}; +use crate::types::addon::{ResourcePath, ResourceResponse}; +use crate::types::profile::Profile; +use crate::types::resource::{MetaItem, MetaItemBehaviorHints, MetaItemPreview, Video}; +use crate::unit_tests::{default_fetch_handler, Request, TestEnv, FETCH_HANDLER, STATES}; +use assert_matches::assert_matches; +use enclose::enclose; +use futures::future; +use std::any::Any; +use std::sync::{Arc, RwLock}; +use stremio_derive::Model; + +#[test] +fn override_selected_default_video_id() { + #[derive(Model, Default, Clone, Debug)] + #[model(TestEnv)] + struct TestModel { + ctx: Ctx, + meta_details: MetaDetails, + } + fn fetch_handler(request: Request) -> TryEnvFuture> { + match request { + Request { url, .. } if url == "https://v3-cinemeta.strem.io/meta/movie/tt1.json" => { + future::ok(Box::new(ResourceResponse::Meta { + meta: MetaItem { + preview: MetaItemPreview { + id: "tt1".to_owned(), + r#type: "movie".to_owned(), + behavior_hints: MetaItemBehaviorHints { + default_video_id: Some("_tt1".to_owned()), + ..Default::default() + }, + ..Default::default() + }, + videos: vec![], + }, + }) as Box) + .boxed_env() + } + Request { url, .. } + if url == "https://v3-cinemeta.strem.io//stream/movie/_tt1.json" => + { + future::ok( + Box::new(ResourceResponse::Streams { streams: vec![] }) as Box + ) + .boxed_env() + } + _ => default_fetch_handler(request), + } + } + let _env_mutex = TestEnv::reset(); + *FETCH_HANDLER.write().unwrap() = Box::new(fetch_handler); + let (runtime, rx) = Runtime::::new( + TestModel { + ctx: Ctx { + profile: Profile { + addons: OFFICIAL_ADDONS + .iter() + .filter(|addon| addon.transport_url == *CINEMETA_URL) + .cloned() + .collect(), + ..Default::default() + }, + ..Default::default() + }, + meta_details: Default::default(), + }, + vec![], + 1000, + ); + let runtime = Arc::new(RwLock::new(runtime)); + TestEnv::run_with_runtime( + rx, + runtime.clone(), + enclose!((runtime) move || { + let runtime = runtime.read().unwrap(); + runtime.dispatch(RuntimeAction { + field: None, + action: Action::Load(ActionLoad::MetaDetails(Selected { + meta_path: ResourcePath { + resource: META_RESOURCE_NAME.to_owned(), + r#type: "movie".to_owned(), + id: "tt1".to_owned(), + extra: vec![] + }, + stream_path: None + })), + }); + }), + ); + let states = STATES.read().unwrap(); + let states = states + .iter() + .map(|state| state.downcast_ref::().unwrap()) + .collect::>(); + assert_eq!(states.len(), 3); + assert_matches!(states[0].meta_details.selected, None); + assert_matches!( + states[1].meta_details.selected, + Some(Selected { + stream_path: None, + .. + }) + ); + assert_matches!( + &states[2].meta_details.selected, + Some(Selected { + stream_path: Some(ResourcePath { + resource, + r#type, + id, + .. + }), + .. + }) if id == "_tt1" && r#type == "movie" && resource == STREAM_RESOURCE_NAME + ); +} + +#[test] +fn override_selected_only_video_id() { + #[derive(Model, Default, Clone, Debug)] + #[model(TestEnv)] + struct TestModel { + ctx: Ctx, + meta_details: MetaDetails, + } + fn fetch_handler(request: Request) -> TryEnvFuture> { + match request { + Request { url, .. } if url == "https://v3-cinemeta.strem.io/meta/movie/tt1.json" => { + future::ok(Box::new(ResourceResponse::Meta { + meta: MetaItem { + preview: MetaItemPreview { + id: "tt1".to_owned(), + r#type: "movie".to_owned(), + ..Default::default() + }, + videos: vec![Video { + id: "_tt1".to_owned(), + ..Default::default() + }], + }, + }) as Box) + .boxed_env() + } + Request { url, .. } + if url == "https://v3-cinemeta.strem.io//stream/movie/_tt1.json" => + { + future::ok( + Box::new(ResourceResponse::Streams { streams: vec![] }) as Box + ) + .boxed_env() + } + _ => default_fetch_handler(request), + } + } + let _env_mutex = TestEnv::reset(); + *FETCH_HANDLER.write().unwrap() = Box::new(fetch_handler); + let (runtime, rx) = Runtime::::new( + TestModel { + ctx: Ctx { + profile: Profile { + addons: OFFICIAL_ADDONS + .iter() + .filter(|addon| addon.transport_url == *CINEMETA_URL) + .cloned() + .collect(), + ..Default::default() + }, + ..Default::default() + }, + meta_details: Default::default(), + }, + vec![], + 1000, + ); + let runtime = Arc::new(RwLock::new(runtime)); + TestEnv::run_with_runtime( + rx, + runtime.clone(), + enclose!((runtime) move || { + let runtime = runtime.read().unwrap(); + runtime.dispatch(RuntimeAction { + field: None, + action: Action::Load(ActionLoad::MetaDetails(Selected { + meta_path: ResourcePath { + resource: META_RESOURCE_NAME.to_owned(), + r#type: "movie".to_owned(), + id: "tt1".to_owned(), + extra: vec![] + }, + stream_path: None + })), + }); + }), + ); + let states = STATES.read().unwrap(); + let states = states + .iter() + .map(|state| state.downcast_ref::().unwrap()) + .collect::>(); + assert_eq!(states.len(), 3); + assert_matches!(states[0].meta_details.selected, None); + assert_matches!( + states[1].meta_details.selected, + Some(Selected { + stream_path: None, + .. + }) + ); + assert_matches!( + &states[2].meta_details.selected, + Some(Selected { + stream_path: Some(ResourcePath { + resource, + r#type, + id, + .. + }), + .. + }) if id == "_tt1" && r#type == "movie" && resource == STREAM_RESOURCE_NAME + ); +} + +#[test] +fn override_selected_meta_id() { + #[derive(Model, Default, Clone, Debug)] + #[model(TestEnv)] + struct TestModel { + ctx: Ctx, + meta_details: MetaDetails, + } + fn fetch_handler(request: Request) -> TryEnvFuture> { + match request { + Request { url, .. } if url == "https://v3-cinemeta.strem.io/meta/movie/tt1.json" => { + future::ok(Box::new(ResourceResponse::Meta { + meta: MetaItem { + preview: MetaItemPreview { + id: "tt1".to_owned(), + r#type: "movie".to_owned(), + ..Default::default() + }, + videos: vec![], + }, + }) as Box) + .boxed_env() + } + Request { url, .. } + if url == "https://v3-cinemeta.strem.io//stream/movie/_tt1.json" => + { + future::ok( + Box::new(ResourceResponse::Streams { streams: vec![] }) as Box + ) + .boxed_env() + } + _ => default_fetch_handler(request), + } + } + let _env_mutex = TestEnv::reset(); + *FETCH_HANDLER.write().unwrap() = Box::new(fetch_handler); + let (runtime, rx) = Runtime::::new( + TestModel { + ctx: Ctx { + profile: Profile { + addons: OFFICIAL_ADDONS + .iter() + .filter(|addon| addon.transport_url == *CINEMETA_URL) + .cloned() + .collect(), + ..Default::default() + }, + ..Default::default() + }, + meta_details: Default::default(), + }, + vec![], + 1000, + ); + let runtime = Arc::new(RwLock::new(runtime)); + TestEnv::run_with_runtime( + rx, + runtime.clone(), + enclose!((runtime) move || { + let runtime = runtime.read().unwrap(); + runtime.dispatch(RuntimeAction { + field: None, + action: Action::Load(ActionLoad::MetaDetails(Selected { + meta_path: ResourcePath { + resource: META_RESOURCE_NAME.to_owned(), + r#type: "movie".to_owned(), + id: "tt1".to_owned(), + extra: vec![] + }, + stream_path: None + })), + }); + }), + ); + let states = STATES.read().unwrap(); + let states = states + .iter() + .map(|state| state.downcast_ref::().unwrap()) + .collect::>(); + assert_eq!(states.len(), 3); + assert_matches!(states[0].meta_details.selected, None); + assert_matches!( + states[1].meta_details.selected, + Some(Selected { + stream_path: None, + .. + }) + ); + assert_matches!( + &states[2].meta_details.selected, + Some(Selected { + stream_path: Some(ResourcePath { + resource, + r#type, + id, + .. + }), + .. + }) if id == "tt1" && r#type == "movie" && resource == STREAM_RESOURCE_NAME + ); +} diff --git a/src/unit_tests/mod.rs b/src/unit_tests/mod.rs index cd2e5d728..442465197 100644 --- a/src/unit_tests/mod.rs +++ b/src/unit_tests/mod.rs @@ -3,9 +3,9 @@ pub use env::*; mod catalog_with_filters; mod ctx; -mod data_export; +mod deep_links; +mod meta_details; mod serde; +mod data_export; mod link; - -mod deep_links; From ecb48473ae3280d0c8d9dedbb08644915c403729 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Tue, 13 Jun 2023 14:12:44 -0700 Subject: [PATCH 42/61] rename meta_item var --- src/models/meta_details.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs index bdc5ffba0..e89145125 100644 --- a/src/models/meta_details.rs +++ b/src/models/meta_details.rs @@ -214,7 +214,7 @@ fn selected_override_update( } else { return Effects::default(); }; - let viable_meta_item = if let Some(viable_meta_item) = meta_items + let meta_item = if let Some(meta_item) = meta_items .iter() .find_map(|meta_item| match &meta_item.content { Some(Loadable::Ready(meta_item)) => Some(Some(meta_item)), @@ -223,17 +223,17 @@ fn selected_override_update( }) .flatten() { - viable_meta_item + meta_item } else { return Effects::default(); }; let video_id = match ( - viable_meta_item.videos.len(), - &viable_meta_item.preview.behavior_hints.default_video_id, + meta_item.videos.len(), + &meta_item.preview.behavior_hints.default_video_id, ) { (_, Some(default_video_id)) => default_video_id.to_owned(), - (1, _) => viable_meta_item.videos.first().unwrap().id.to_owned(), - (0, None) => viable_meta_item.preview.id.to_owned(), + (1, _) => meta_item.videos.first().unwrap().id.to_owned(), + (0, None) => meta_item.preview.id.to_owned(), _ => return Effects::default(), }; eq_update( From fcb00b336977ee50f4ccb5d6519443f20dba71ca Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Tue, 13 Jun 2023 14:21:51 -0700 Subject: [PATCH 43/61] remove stream request handlers in tests --- .../meta_details/override_selected.rs | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/src/unit_tests/meta_details/override_selected.rs b/src/unit_tests/meta_details/override_selected.rs index f10b06a0e..c5fb7352c 100644 --- a/src/unit_tests/meta_details/override_selected.rs +++ b/src/unit_tests/meta_details/override_selected.rs @@ -41,14 +41,6 @@ fn override_selected_default_video_id() { }) as Box) .boxed_env() } - Request { url, .. } - if url == "https://v3-cinemeta.strem.io//stream/movie/_tt1.json" => - { - future::ok( - Box::new(ResourceResponse::Streams { streams: vec![] }) as Box - ) - .boxed_env() - } _ => default_fetch_handler(request), } } @@ -146,14 +138,6 @@ fn override_selected_only_video_id() { }) as Box) .boxed_env() } - Request { url, .. } - if url == "https://v3-cinemeta.strem.io//stream/movie/_tt1.json" => - { - future::ok( - Box::new(ResourceResponse::Streams { streams: vec![] }) as Box - ) - .boxed_env() - } _ => default_fetch_handler(request), } } @@ -248,14 +232,6 @@ fn override_selected_meta_id() { }) as Box) .boxed_env() } - Request { url, .. } - if url == "https://v3-cinemeta.strem.io//stream/movie/_tt1.json" => - { - future::ok( - Box::new(ResourceResponse::Streams { streams: vec![] }) as Box - ) - .boxed_env() - } _ => default_fetch_handler(request), } } From d81e8584fade6588a4fc235dcbdd0f169aedcef7 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Sat, 17 Jun 2023 11:23:08 -0700 Subject: [PATCH 44/61] derive Debug trait recargless of the cfg --- src/models/ctx/update_streams.rs | 5 ++--- src/types/streams/streams_bucket.rs | 11 ++++------- src/types/streams/streams_item.rs | 6 ++---- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/models/ctx/update_streams.rs b/src/models/ctx/update_streams.rs index 3ef5e2420..40640229a 100644 --- a/src/models/ctx/update_streams.rs +++ b/src/models/ctx/update_streams.rs @@ -1,11 +1,10 @@ -use enclose::enclose; -use futures::FutureExt; - use crate::constants::STREAMS_STORAGE_KEY; use crate::models::ctx::{CtxError, CtxStatus}; use crate::runtime::msg::{Action, ActionCtx, Event, Internal, Msg}; use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt}; use crate::types::streams::{StreamsBucket, StreamsItem}; +use enclose::enclose; +use futures::FutureExt; pub fn update_streams( streams: &mut StreamsBucket, diff --git a/src/types/streams/streams_bucket.rs b/src/types/streams/streams_bucket.rs index 2579893e7..57682ed45 100644 --- a/src/types/streams/streams_bucket.rs +++ b/src/types/streams/streams_bucket.rs @@ -1,14 +1,11 @@ -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; -use serde_with::serde_as; - use crate::types::profile::UID; use crate::types::streams::StreamsItem; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use std::collections::HashMap; #[serde_as] -#[derive(Default, Clone, PartialEq, Serialize, Deserialize)] -#[cfg_attr(debug_assertions, derive(Debug))] +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct StreamsBucket { pub uid: UID, #[serde_as(as = "Vec<(_, _)>")] diff --git a/src/types/streams/streams_item.rs b/src/types/streams/streams_item.rs index c293696ab..906033e0e 100644 --- a/src/types/streams/streams_item.rs +++ b/src/types/streams/streams_item.rs @@ -1,13 +1,11 @@ +use crate::types::resource::Stream; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_with::serde_as; use url::Url; -use crate::types::resource::Stream; - #[serde_as] -#[derive(Clone, PartialEq, Serialize, Deserialize)] -#[cfg_attr(debug_assertions, derive(Debug))] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct StreamsItem { pub stream: Stream, pub transport_url: Url, From b7423a8750f3161230cdab7d3a45ce5f492ac7a3 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Sat, 17 Jun 2023 11:26:03 -0700 Subject: [PATCH 45/61] refactor StreamLoaded with named props --- src/models/ctx/update_streams.rs | 12 ++++++------ src/models/player.rs | 12 ++++++------ src/runtime/msg/internal.rs | 7 ++++++- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/models/ctx/update_streams.rs b/src/models/ctx/update_streams.rs index 40640229a..27d933fbb 100644 --- a/src/models/ctx/update_streams.rs +++ b/src/models/ctx/update_streams.rs @@ -21,12 +21,12 @@ pub fn update_streams( Effects::none().unchanged() } } - Msg::Internal(Internal::StreamLoaded( - Some(meta_item_id), - Some(video_id), - Some(transport_url), + Msg::Internal(Internal::StreamLoaded { stream, - )) => { + meta_id: Some(meta_id), + video_id: Some(video_id), + transport_url: Some(transport_url), + }) => { let streams_item = StreamsItem { stream: stream.to_owned(), transport_url: transport_url.to_owned(), @@ -34,7 +34,7 @@ pub fn update_streams( }; streams .items - .insert((meta_item_id.to_owned(), video_id.to_owned()), streams_item); + .insert((meta_id.to_owned(), video_id.to_owned()), streams_item); Effects::msg(Msg::Internal(Internal::StreamsChanged(false))) } Msg::Internal(Internal::StreamsChanged(persisted)) if !persisted => { diff --git a/src/models/player.rs b/src/models/player.rs index 1b35d6f04..141778746 100644 --- a/src/models/player.rs +++ b/src/models/player.rs @@ -127,21 +127,21 @@ impl UpdateWithCtx for Player { &selected.stream_request, &selected.meta_request, )) { - Effects::msg(Msg::Internal(Internal::StreamLoaded( - selected + Effects::msg(Msg::Internal(Internal::StreamLoaded { + stream: selected.stream.to_owned(), + meta_id: selected .meta_request .as_ref() .map(|meta_request| meta_request.path.id.to_owned()), - selected + video_id: selected .stream_request .as_ref() .map(|stream_request| stream_request.path.id.to_owned()), - selected + transport_url: selected .stream_request .as_ref() .map(|stream_request| stream_request.base.to_owned()), - selected.stream.to_owned(), - ))) + })) .unchanged() } else { Effects::none().unchanged() diff --git a/src/runtime/msg/internal.rs b/src/runtime/msg/internal.rs index 1593d38e5..6132432a5 100644 --- a/src/runtime/msg/internal.rs +++ b/src/runtime/msg/internal.rs @@ -45,7 +45,12 @@ pub enum Internal { /// Dispatched when addons needs to be installed. InstallAddon(Descriptor), /// Dispatched when a new stream is loaded into the Player. - StreamLoaded(Option, Option, Option, Stream), + StreamLoaded { + stream: Stream, + meta_id: Option, + video_id: Option, + transport_url: Option, + }, /// 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. From 6800963194ca69f13d3636abbba4ca96a9e74ce8 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Sat, 17 Jun 2023 11:31:51 -0700 Subject: [PATCH 46/61] add StreamsItemKey struct for clarity --- src/models/ctx/update_streams.rs | 10 ++++++---- src/types/streams/streams_bucket.rs | 8 +++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/models/ctx/update_streams.rs b/src/models/ctx/update_streams.rs index 27d933fbb..560225fc3 100644 --- a/src/models/ctx/update_streams.rs +++ b/src/models/ctx/update_streams.rs @@ -2,7 +2,7 @@ use crate::constants::STREAMS_STORAGE_KEY; use crate::models::ctx::{CtxError, CtxStatus}; use crate::runtime::msg::{Action, ActionCtx, Event, Internal, Msg}; use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt}; -use crate::types::streams::{StreamsBucket, StreamsItem}; +use crate::types::streams::{StreamsBucket, StreamsItem, StreamsItemKey}; use enclose::enclose; use futures::FutureExt; @@ -27,14 +27,16 @@ pub fn update_streams( video_id: Some(video_id), transport_url: Some(transport_url), }) => { + let key = StreamsItemKey { + meta_id: meta_id.to_owned(), + video_id: video_id.to_owned(), + }; let streams_item = StreamsItem { stream: stream.to_owned(), transport_url: transport_url.to_owned(), mtime: E::now(), }; - streams - .items - .insert((meta_id.to_owned(), video_id.to_owned()), streams_item); + streams.items.insert(key, streams_item); Effects::msg(Msg::Internal(Internal::StreamsChanged(false))) } Msg::Internal(Internal::StreamsChanged(persisted)) if !persisted => { diff --git a/src/types/streams/streams_bucket.rs b/src/types/streams/streams_bucket.rs index 57682ed45..f621c45f2 100644 --- a/src/types/streams/streams_bucket.rs +++ b/src/types/streams/streams_bucket.rs @@ -4,12 +4,18 @@ use serde::{Deserialize, Serialize}; use serde_with::serde_as; use std::collections::HashMap; +#[derive(Default, Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct StreamsItemKey { + pub meta_id: String, + pub video_id: String, +} + #[serde_as] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct StreamsBucket { pub uid: UID, #[serde_as(as = "Vec<(_, _)>")] - pub items: HashMap<(String, String), StreamsItem>, + pub items: HashMap, } impl StreamsBucket { From 80832f7278b19e862fbce18aaaed829b5d135be0 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Sat, 17 Jun 2023 12:09:24 -0700 Subject: [PATCH 47/61] clone model state only in test cfg --- src/runtime/runtime.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/runtime/runtime.rs b/src/runtime/runtime.rs index 1900c73d4..8c9f05da8 100644 --- a/src/runtime/runtime.rs +++ b/src/runtime/runtime.rs @@ -13,7 +13,7 @@ use std::sync::{Arc, LockResult, RwLock, RwLockReadGuard}; #[derive(Serialize, Debug, PartialEq)] #[serde(tag = "name", content = "args")] pub enum RuntimeEvent> { - NewState(Vec, M), + NewState(Vec, #[cfg(test)] M), CoreEvent(Event), } @@ -78,8 +78,13 @@ where } fn handle_effects(&self, effects: Vec, fields: Vec) { if !fields.is_empty() { + #[cfg(test)] let model = self.model.read().expect("model read failed"); - self.emit(RuntimeEvent::::NewState(fields, model.to_owned())); + self.emit(RuntimeEvent::::NewState( + fields, + #[cfg(test)] + model.to_owned(), + )); }; effects .into_iter() From c29907b2ccadc194e6fd93517d662b0379bb2dcd Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Sat, 17 Jun 2023 12:17:15 -0700 Subject: [PATCH 48/61] revert deriving Hash --- src/types/addon/descriptor.rs | 4 ++-- src/types/addon/manifest.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/types/addon/descriptor.rs b/src/types/addon/descriptor.rs index e4e7d1d26..fc6618677 100644 --- a/src/types/addon/descriptor.rs +++ b/src/types/addon/descriptor.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use url::Url; /// Addon descriptor -#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Descriptor { pub manifest: Manifest, @@ -19,7 +19,7 @@ pub struct DescriptorPreview { pub transport_url: Url, } -#[derive(Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] +#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct DescriptorFlags { #[serde(default)] diff --git a/src/types/addon/manifest.rs b/src/types/addon/manifest.rs index 93d6c00f6..a31209e23 100644 --- a/src/types/addon/manifest.rs +++ b/src/types/addon/manifest.rs @@ -11,7 +11,7 @@ use std::borrow::Cow; use url::Url; #[serde_as] -#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(test, derive(Derivative))] #[cfg_attr(test, derivative(Default))] #[serde(rename_all = "camelCase")] @@ -103,7 +103,7 @@ pub struct ManifestPreview { pub behavior_hints: ManifestBehaviorHints, } -#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[serde(untagged)] pub enum ManifestResource { Short(String), @@ -125,7 +125,7 @@ impl ManifestResource { } } -#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ManifestCatalog { pub id: String, @@ -178,7 +178,7 @@ impl UniqueVecAdapter for ManifestCatalogUniqueVecAdapter { } #[serde_as] -#[derive(Derivative, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Derivative, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derivative(Default)] #[serde(untagged)] pub enum ManifestExtra { @@ -220,7 +220,7 @@ impl ManifestExtra { } #[serde_as] -#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ExtraProp { pub name: String, @@ -267,7 +267,7 @@ impl<'de> DeserializeAs<'de, ExtraProp> for ExtraPropValid { } } -#[derive(Clone, Deref, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] +#[derive(Clone, Deref, PartialEq, Eq, Serialize, Deserialize, Debug)] pub struct OptionsLimit(pub usize); impl Default for OptionsLimit { @@ -276,7 +276,7 @@ impl Default for OptionsLimit { } } -#[derive(Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] +#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ManifestBehaviorHints { #[serde(default)] From 8668ea14d0395b9c9d97092f4d30123dd87f0949 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Sat, 17 Jun 2023 12:33:54 -0700 Subject: [PATCH 49/61] optimize the logic of computing the diff of transport urls --- src/models/ctx/update_profile.rs | 44 ++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/models/ctx/update_profile.rs b/src/models/ctx/update_profile.rs index 7bb318e78..0211b9982 100644 --- a/src/models/ctx/update_profile.rs +++ b/src/models/ctx/update_profile.rs @@ -83,14 +83,22 @@ pub fn update_profile( .unwrap_or_else(|| profile_addon.to_owned()) }) .collect::>(); - let local_addons = profile.addons.iter().cloned().collect::>(); - let remote_addons = next_addons.iter().cloned().collect::>(); - let added_addons = &remote_addons - &local_addons; - let removed_addon = &local_addons - &remote_addons; - let transport_urls = added_addons + let prev_transport_urls = profile + .addons + .iter() + .map(|addon| &addon.transport_url) + .cloned() + .collect::>(); + let next_transport_urls = next_addons + .iter() + .map(|addon| &addon.transport_url) + .cloned() + .collect::>(); + let added_transport_urls = &next_transport_urls - &prev_transport_urls; + let removed_transport_urls = &prev_transport_urls - &next_transport_urls; + let transport_urls = added_transport_urls .into_iter() - .chain(removed_addon.into_iter()) - .map(|addon| addon.transport_url) + .chain(removed_transport_urls.into_iter()) .collect(); if profile.addons != next_addons { profile.addons = next_addons; @@ -263,14 +271,22 @@ pub fn update_profile( result, )) if profile.auth_key() == Some(auth_key) => match result { Ok(addons) => { - let local_addons = profile.addons.iter().cloned().collect::>(); - let remote_addons = addons.iter().cloned().collect::>(); - let added_addons = &remote_addons - &local_addons; - let removed_addon = &local_addons - &remote_addons; - let transport_urls = added_addons + let prev_transport_urls = profile + .addons + .iter() + .map(|addon| &addon.transport_url) + .cloned() + .collect::>(); + let next_transport_urls = addons + .iter() + .map(|addon| &addon.transport_url) + .cloned() + .collect::>(); + let added_transport_urls = &next_transport_urls - &prev_transport_urls; + let removed_transport_urls = &prev_transport_urls - &next_transport_urls; + let transport_urls = added_transport_urls .into_iter() - .chain(removed_addon.into_iter()) - .map(|addon| addon.transport_url) + .chain(removed_transport_urls.into_iter()) .collect(); if profile.addons != *addons { profile.addons = addons.to_owned(); From 7679547414df42267a528726f0a6ef163853665c Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Sun, 18 Jun 2023 01:14:34 -0700 Subject: [PATCH 50/61] meta_id and video_id added to StreamsItem --- src/models/ctx/update_streams.rs | 2 ++ src/types/streams/streams_item.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/models/ctx/update_streams.rs b/src/models/ctx/update_streams.rs index 560225fc3..434a06c7b 100644 --- a/src/models/ctx/update_streams.rs +++ b/src/models/ctx/update_streams.rs @@ -33,6 +33,8 @@ pub fn update_streams( }; let streams_item = StreamsItem { stream: stream.to_owned(), + meta_id: meta_id.to_owned(), + video_id: video_id.to_owned(), transport_url: transport_url.to_owned(), mtime: E::now(), }; diff --git a/src/types/streams/streams_item.rs b/src/types/streams/streams_item.rs index 906033e0e..eb7cbfcca 100644 --- a/src/types/streams/streams_item.rs +++ b/src/types/streams/streams_item.rs @@ -8,6 +8,8 @@ use url::Url; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct StreamsItem { pub stream: Stream, + pub meta_id: String, + pub video_id: String, pub transport_url: Url, pub mtime: DateTime, } From 5c9f254f2b77a1256671abc6f383c58ff799a4d8 Mon Sep 17 00:00:00 2001 From: unclekingpin <125216544+unclekingpin@users.noreply.github.com> Date: Wed, 21 Jun 2023 22:08:48 +0300 Subject: [PATCH 51/61] Rename StreamsItem keys to camelCase Co-authored-by: Lachezar Lechev <8925621+elpiel@users.noreply.github.com> --- src/types/streams/streams_item.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/streams/streams_item.rs b/src/types/streams/streams_item.rs index eb7cbfcca..a120bf1fb 100644 --- a/src/types/streams/streams_item.rs +++ b/src/types/streams/streams_item.rs @@ -6,6 +6,7 @@ use url::Url; #[serde_as] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StreamsItem { pub stream: Stream, pub meta_id: String, From ec2f770305616a80660d164c3d8d128f0771ab83 Mon Sep 17 00:00:00 2001 From: unclekingpin <125216544+unclekingpin@users.noreply.github.com> Date: Wed, 21 Jun 2023 22:10:56 +0300 Subject: [PATCH 52/61] serialize mtime as _mtime for consistency --- src/types/streams/streams_item.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types/streams/streams_item.rs b/src/types/streams/streams_item.rs index a120bf1fb..a1597ad3e 100644 --- a/src/types/streams/streams_item.rs +++ b/src/types/streams/streams_item.rs @@ -12,5 +12,7 @@ pub struct StreamsItem { pub meta_id: String, pub video_id: String, pub transport_url: Url, + /// Modification time + #[serde(rename = "_mtime")] pub mtime: DateTime, } From d1b073254b821d9ddd2a22486423db0c6242e8e4 Mon Sep 17 00:00:00 2001 From: unclekingpin <125216544+unclekingpin@users.noreply.github.com> Date: Wed, 21 Jun 2023 22:13:48 +0300 Subject: [PATCH 53/61] Serialize StreamsItemKey key as camelCase --- src/types/streams/streams_bucket.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/streams/streams_bucket.rs b/src/types/streams/streams_bucket.rs index f621c45f2..c96bd0d3b 100644 --- a/src/types/streams/streams_bucket.rs +++ b/src/types/streams/streams_bucket.rs @@ -5,6 +5,7 @@ use serde_with::serde_as; use std::collections::HashMap; #[derive(Default, Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct StreamsItemKey { pub meta_id: String, pub video_id: String, From 0b919fce096914490413e5c544b628454f8e6bd4 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Fri, 30 Jun 2023 22:14:46 -0700 Subject: [PATCH 54/61] use match instead of iflet --- src/models/meta_details.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs index e89145125..b3617c0b4 100644 --- a/src/models/meta_details.rs +++ b/src/models/meta_details.rs @@ -205,16 +205,14 @@ fn selected_override_update( selected: &mut Option, meta_items: &Vec>, ) -> Effects { - let meta_path = if let Some(Selected { - meta_path, - stream_path: None, - }) = &selected - { - meta_path - } else { - return Effects::default(); + let meta_path = match &selected { + Some(Selected { + meta_path, + stream_path: None, + }) => meta_path, + None => return Effects::default(), }; - let meta_item = if let Some(meta_item) = meta_items + let meta_item = match meta_items .iter() .find_map(|meta_item| match &meta_item.content { Some(Loadable::Ready(meta_item)) => Some(Some(meta_item)), @@ -223,9 +221,8 @@ fn selected_override_update( }) .flatten() { - meta_item - } else { - return Effects::default(); + Some(meta_item) => meta_item, + _ => return Effects::default(), }; let video_id = match ( meta_item.videos.len(), From c1aad45b1abf1da30410b85ef526dfae1516cf0a Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Sun, 2 Jul 2023 03:26:42 -0700 Subject: [PATCH 55/61] remove the case with single video --- src/models/meta_details.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs index b3617c0b4..db0f17a15 100644 --- a/src/models/meta_details.rs +++ b/src/models/meta_details.rs @@ -229,7 +229,6 @@ fn selected_override_update( &meta_item.preview.behavior_hints.default_video_id, ) { (_, Some(default_video_id)) => default_video_id.to_owned(), - (1, _) => meta_item.videos.first().unwrap().id.to_owned(), (0, None) => meta_item.preview.id.to_owned(), _ => return Effects::default(), }; From 1fad3c59a5ed56cc574bb483a33a7e89f73a2440 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Wed, 5 Jul 2023 11:44:34 -0700 Subject: [PATCH 56/61] fix non-exhaustive match --- src/models/meta_details.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs index db0f17a15..33e1f3524 100644 --- a/src/models/meta_details.rs +++ b/src/models/meta_details.rs @@ -210,7 +210,7 @@ fn selected_override_update( meta_path, stream_path: None, }) => meta_path, - None => return Effects::default(), + _ => return Effects::default(), }; let meta_item = match meta_items .iter() From 1866e6ab004cc315b45d4b13c2a7aa4b07e8aee2 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Wed, 5 Jul 2023 11:49:07 -0700 Subject: [PATCH 57/61] remove the test for only video id --- .../meta_details/override_selected.rs | 99 +------------------ 1 file changed, 1 insertion(+), 98 deletions(-) diff --git a/src/unit_tests/meta_details/override_selected.rs b/src/unit_tests/meta_details/override_selected.rs index c5fb7352c..e12c82516 100644 --- a/src/unit_tests/meta_details/override_selected.rs +++ b/src/unit_tests/meta_details/override_selected.rs @@ -5,7 +5,7 @@ use crate::runtime::msg::{Action, ActionLoad}; use crate::runtime::{EnvFutureExt, Runtime, RuntimeAction, TryEnvFuture}; use crate::types::addon::{ResourcePath, ResourceResponse}; use crate::types::profile::Profile; -use crate::types::resource::{MetaItem, MetaItemBehaviorHints, MetaItemPreview, Video}; +use crate::types::resource::{MetaItem, MetaItemBehaviorHints, MetaItemPreview}; use crate::unit_tests::{default_fetch_handler, Request, TestEnv, FETCH_HANDLER, STATES}; use assert_matches::assert_matches; use enclose::enclose; @@ -112,103 +112,6 @@ fn override_selected_default_video_id() { ); } -#[test] -fn override_selected_only_video_id() { - #[derive(Model, Default, Clone, Debug)] - #[model(TestEnv)] - struct TestModel { - ctx: Ctx, - meta_details: MetaDetails, - } - fn fetch_handler(request: Request) -> TryEnvFuture> { - match request { - Request { url, .. } if url == "https://v3-cinemeta.strem.io/meta/movie/tt1.json" => { - future::ok(Box::new(ResourceResponse::Meta { - meta: MetaItem { - preview: MetaItemPreview { - id: "tt1".to_owned(), - r#type: "movie".to_owned(), - ..Default::default() - }, - videos: vec![Video { - id: "_tt1".to_owned(), - ..Default::default() - }], - }, - }) as Box) - .boxed_env() - } - _ => default_fetch_handler(request), - } - } - let _env_mutex = TestEnv::reset(); - *FETCH_HANDLER.write().unwrap() = Box::new(fetch_handler); - let (runtime, rx) = Runtime::::new( - TestModel { - ctx: Ctx { - profile: Profile { - addons: OFFICIAL_ADDONS - .iter() - .filter(|addon| addon.transport_url == *CINEMETA_URL) - .cloned() - .collect(), - ..Default::default() - }, - ..Default::default() - }, - meta_details: Default::default(), - }, - vec![], - 1000, - ); - let runtime = Arc::new(RwLock::new(runtime)); - TestEnv::run_with_runtime( - rx, - runtime.clone(), - enclose!((runtime) move || { - let runtime = runtime.read().unwrap(); - runtime.dispatch(RuntimeAction { - field: None, - action: Action::Load(ActionLoad::MetaDetails(Selected { - meta_path: ResourcePath { - resource: META_RESOURCE_NAME.to_owned(), - r#type: "movie".to_owned(), - id: "tt1".to_owned(), - extra: vec![] - }, - stream_path: None - })), - }); - }), - ); - let states = STATES.read().unwrap(); - let states = states - .iter() - .map(|state| state.downcast_ref::().unwrap()) - .collect::>(); - assert_eq!(states.len(), 3); - assert_matches!(states[0].meta_details.selected, None); - assert_matches!( - states[1].meta_details.selected, - Some(Selected { - stream_path: None, - .. - }) - ); - assert_matches!( - &states[2].meta_details.selected, - Some(Selected { - stream_path: Some(ResourcePath { - resource, - r#type, - id, - .. - }), - .. - }) if id == "_tt1" && r#type == "movie" && resource == STREAM_RESOURCE_NAME - ); -} - #[test] fn override_selected_meta_id() { #[derive(Model, Default, Clone, Debug)] From a95cc688c46371bfbf0bf2bdb9da4377487bcabb Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Wed, 5 Jul 2023 11:52:33 -0700 Subject: [PATCH 58/61] fix clippy --- src/models/meta_details.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs index 33e1f3524..a4c8bc155 100644 --- a/src/models/meta_details.rs +++ b/src/models/meta_details.rs @@ -203,7 +203,7 @@ fn library_item_sync(library_item: &Option, profile: &Profile) -> E fn selected_override_update( selected: &mut Option, - meta_items: &Vec>, + meta_items: &[ResourceLoadable], ) -> Effects { let meta_path = match &selected { Some(Selected { From b3360d5dc7b37c157bebb824b3c009f051c00249 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Wed, 5 Jul 2023 12:02:26 -0700 Subject: [PATCH 59/61] derive clone for all models --- src/models/addon_details.rs | 2 +- src/models/catalogs_with_extra.rs | 2 +- src/models/continue_watching_preview.rs | 2 +- src/models/installed_addons_with_filters.rs | 6 +++--- src/models/library_by_type.rs | 6 +++--- src/models/library_with_filters.rs | 10 +++++----- src/models/notifications.rs | 2 +- src/models/player.rs | 2 +- src/models/streaming_server.rs | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/models/addon_details.rs b/src/models/addon_details.rs index 238ad041d..98deb0e4f 100644 --- a/src/models/addon_details.rs +++ b/src/models/addon_details.rs @@ -13,7 +13,7 @@ pub struct Selected { pub transport_url: Url, } -#[derive(Default, Serialize)] +#[derive(Default, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct AddonDetails { pub selected: Option, diff --git a/src/models/catalogs_with_extra.rs b/src/models/catalogs_with_extra.rs index 591d7050a..17fc5750a 100644 --- a/src/models/catalogs_with_extra.rs +++ b/src/models/catalogs_with_extra.rs @@ -23,7 +23,7 @@ pub type CatalogPage = ResourceLoadable>; pub type Catalog = Vec>; -#[derive(Default, Serialize, Debug)] +#[derive(Default, Clone, Serialize, Debug)] pub struct CatalogsWithExtra { pub selected: Option, pub catalogs: Vec>, diff --git a/src/models/continue_watching_preview.rs b/src/models/continue_watching_preview.rs index 59a6f485b..267bb09f6 100644 --- a/src/models/continue_watching_preview.rs +++ b/src/models/continue_watching_preview.rs @@ -7,7 +7,7 @@ use crate::types::library::{LibraryBucket, LibraryItem}; use lazysort::SortedBy; use serde::Serialize; -#[derive(Default, Serialize, Debug)] +#[derive(Default, Clone, Serialize, Debug)] #[serde(rename_all = "camelCase")] /// The continue watching section in the app pub struct ContinueWatchingPreview { diff --git a/src/models/installed_addons_with_filters.rs b/src/models/installed_addons_with_filters.rs index 95ad780cd..13dfa9d65 100644 --- a/src/models/installed_addons_with_filters.rs +++ b/src/models/installed_addons_with_filters.rs @@ -19,19 +19,19 @@ pub struct Selected { pub request: InstalledAddonsRequest, } -#[derive(PartialEq, Eq, Serialize)] +#[derive(Clone, PartialEq, Eq, Serialize)] pub struct SelectableType { pub r#type: Option, pub selected: bool, pub request: InstalledAddonsRequest, } -#[derive(Default, PartialEq, Eq, Serialize)] +#[derive(Default, Clone, PartialEq, Eq, Serialize)] pub struct Selectable { pub types: Vec, } -#[derive(Default, Serialize)] +#[derive(Default, Clone, Serialize)] pub struct InstalledAddonsWithFilters { pub selected: Option, pub selectable: Selectable, diff --git a/src/models/library_by_type.rs b/src/models/library_by_type.rs index d2bb0627c..90ca6550d 100644 --- a/src/models/library_by_type.rs +++ b/src/models/library_by_type.rs @@ -18,13 +18,13 @@ pub struct Selected { pub sort: Sort, } -#[derive(PartialEq, Eq, Serialize, Debug)] +#[derive(Clone, PartialEq, Eq, Serialize, Debug)] pub struct SelectableSort { pub sort: Sort, pub selected: bool, } -#[derive(Default, PartialEq, Eq, Serialize, Debug)] +#[derive(Default, Clone, PartialEq, Eq, Serialize, Debug)] pub struct Selectable { pub sorts: Vec, } @@ -33,7 +33,7 @@ pub type CatalogPage = Vec; pub type Catalog = Vec; -#[derive(Derivative, Serialize, Debug)] +#[derive(Derivative, Serialize, Debug, Clone)] #[derivative(Default(bound = ""))] pub struct LibraryByType { pub selected: Option, diff --git a/src/models/library_with_filters.rs b/src/models/library_with_filters.rs index 80301e9e4..cb30f5081 100644 --- a/src/models/library_with_filters.rs +++ b/src/models/library_with_filters.rs @@ -69,26 +69,26 @@ pub struct Selected { pub request: LibraryRequest, } -#[derive(PartialEq, Eq, Serialize, Debug)] +#[derive(Clone, PartialEq, Eq, Serialize, Debug)] pub struct SelectableType { pub r#type: Option, pub selected: bool, pub request: LibraryRequest, } -#[derive(PartialEq, Eq, Serialize, Debug)] +#[derive(Clone, PartialEq, Eq, Serialize, Debug)] pub struct SelectableSort { pub sort: Sort, pub selected: bool, pub request: LibraryRequest, } -#[derive(PartialEq, Eq, Serialize, Debug)] +#[derive(Clone, PartialEq, Eq, Serialize, Debug)] pub struct SelectablePage { pub request: LibraryRequest, } -#[derive(Default, PartialEq, Eq, Serialize, Debug)] +#[derive(Default, Clone, PartialEq, Eq, Serialize, Debug)] pub struct Selectable { pub types: Vec, pub sorts: Vec, @@ -96,7 +96,7 @@ pub struct Selectable { pub next_page: Option, } -#[derive(Derivative, Serialize, Debug)] +#[derive(Derivative, Clone, Serialize, Debug)] #[derivative(Default(bound = ""))] pub struct LibraryWithFilters { pub selected: Option, diff --git a/src/models/notifications.rs b/src/models/notifications.rs index a7f2ab604..99919d5b4 100644 --- a/src/models/notifications.rs +++ b/src/models/notifications.rs @@ -15,7 +15,7 @@ const MAX_PER_REQUEST: usize = 50; // The name of the extra property const LAST_VID_IDS: &str = "lastVideosIds"; -#[derive(Default, Serialize)] +#[derive(Default, Clone, Serialize)] pub struct Notifications { pub groups: Vec>>, } diff --git a/src/models/player.rs b/src/models/player.rs index 141778746..7c4b5a5a4 100644 --- a/src/models/player.rs +++ b/src/models/player.rs @@ -71,7 +71,7 @@ pub struct Selected { pub video_params: Option, } -#[derive(Default, Derivative, Serialize, Debug)] +#[derive(Default, Clone, Derivative, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Player { pub selected: Option, diff --git a/src/models/streaming_server.rs b/src/models/streaming_server.rs index 3bb756544..da5500564 100644 --- a/src/models/streaming_server.rs +++ b/src/models/streaming_server.rs @@ -48,14 +48,14 @@ pub struct StatisticsRequest { pub file_idx: u16, } -#[derive(Serialize, Debug)] +#[derive(Clone, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Selected { pub transport_url: Url, pub statistics: Option, } -#[derive(Serialize, Debug)] +#[derive(Clone, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct StreamingServer { pub selected: Selected, From febc296eabb1b683be84fa037209a34428766b2c Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Wed, 5 Jul 2023 12:03:45 -0700 Subject: [PATCH 60/61] derive Clone for ContinueWatchingFilter --- src/models/library_with_filters.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/library_with_filters.rs b/src/models/library_with_filters.rs index cb30f5081..20129919e 100644 --- a/src/models/library_with_filters.rs +++ b/src/models/library_with_filters.rs @@ -18,7 +18,7 @@ pub trait LibraryFilter { fn predicate(library_item: &LibraryItem) -> bool; } -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum ContinueWatchingFilter {} impl LibraryFilter for ContinueWatchingFilter { From 8733482cdfefbe53d7a3570297fe589979a99829 Mon Sep 17 00:00:00 2001 From: unclekingpin Date: Wed, 5 Jul 2023 12:04:49 -0700 Subject: [PATCH 61/61] derive Clone for NotRemovedFilter --- src/models/library_with_filters.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/library_with_filters.rs b/src/models/library_with_filters.rs index 20129919e..096dbbfad 100644 --- a/src/models/library_with_filters.rs +++ b/src/models/library_with_filters.rs @@ -27,7 +27,7 @@ impl LibraryFilter for ContinueWatchingFilter { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum NotRemovedFilter {} impl LibraryFilter for NotRemovedFilter {