From 3f2f332c573628db21c3d17216feac91845e2e90 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 12 Oct 2023 13:03:37 +0200 Subject: [PATCH 1/4] fix(ctx): remove related streams from bucket when uninstalling addon --- src/models/ctx/ctx.rs | 9 ++++++--- src/models/ctx/update_profile.rs | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/models/ctx/ctx.rs b/src/models/ctx/ctx.rs index 26a900a91..c8be16888 100644 --- a/src/models/ctx/ctx.rs +++ b/src/models/ctx/ctx.rs @@ -81,7 +81,8 @@ impl Update for Ctx { Some(auth_key) => Effects::one(delete_session::(auth_key)).unchanged(), _ => Effects::none().unchanged(), }; - let profile_effects = update_profile::(&mut self.profile, &self.status, msg); + let profile_effects = + update_profile::(&mut self.profile, &mut self.streams, &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); @@ -110,7 +111,8 @@ impl Update for Ctx { .join(notifications_effects) } Msg::Internal(Internal::CtxAuthResult(auth_request, result)) => { - let profile_effects = update_profile::(&mut self.profile, &self.status, msg); + let profile_effects = + update_profile::(&mut self.profile, &mut self.streams, &self.status, msg); let library_effects = update_library::(&mut self.library, &self.profile, &self.status, msg); let trakt_addon_effects = update_trakt_addon::( @@ -157,7 +159,8 @@ impl Update for Ctx { .join(ctx_effects) } _ => { - let profile_effects = update_profile::(&mut self.profile, &self.status, msg); + let profile_effects = + update_profile::(&mut self.profile, &mut self.streams, &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); diff --git a/src/models/ctx/update_profile.rs b/src/models/ctx/update_profile.rs index 9d79fe946..e04c73c13 100644 --- a/src/models/ctx/update_profile.rs +++ b/src/models/ctx/update_profile.rs @@ -7,12 +7,14 @@ use crate::types::api::{ fetch_api, APIError, APIRequest, APIResult, CollectionResponse, SuccessResponse, }; use crate::types::profile::{Auth, AuthKey, Profile, Settings, User}; +use crate::types::streams::StreamsBucket; use enclose::enclose; use futures::{future, FutureExt, TryFutureExt}; use std::collections::HashSet; pub fn update_profile( profile: &mut Profile, + streams: &mut StreamsBucket, status: &CtxStatus, msg: &Msg, ) -> Effects { @@ -159,6 +161,12 @@ pub fn update_profile( if let Some(addon_position) = addon_position { if !profile.addons[addon_position].flags.protected && !addon.flags.protected { profile.addons.remove(addon_position); + + // Remove stream related to this addon from the streams bucket + streams + .items + .retain(|_key, item| item.stream_transport_url != addon.transport_url); + let push_to_api_effects = match profile.auth_key() { Some(auth_key) => Effects::one(push_addons_to_api::( profile.addons.to_owned(), From ea24e511eec292c31866570b9c98af957fffaa7c Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 12 Oct 2023 13:03:54 +0200 Subject: [PATCH 2/4] test(ctx): add test for cleaning streams bucket --- src/unit_tests/ctx/uninstall_addon.rs | 119 +++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/src/unit_tests/ctx/uninstall_addon.rs b/src/unit_tests/ctx/uninstall_addon.rs index 80a0adaee..0810c7331 100644 --- a/src/unit_tests/ctx/uninstall_addon.rs +++ b/src/unit_tests/ctx/uninstall_addon.rs @@ -7,7 +7,8 @@ use crate::types::api::{APIResult, SuccessResponse}; use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; use crate::types::profile::{Auth, AuthKey, GDPRConsent, Profile, User}; -use crate::types::streams::StreamsBucket; +use crate::types::resource::{Stream, StreamBehaviorHints, StreamSource}; +use crate::types::streams::{StreamsBucket, StreamsItem, StreamsItemKey}; use crate::types::True; use crate::unit_tests::{ default_fetch_handler, Request, TestEnv, FETCH_HANDLER, REQUESTS, STORAGE, @@ -370,3 +371,119 @@ fn actionctx_uninstalladdon_not_installed() { "No requests have been sent" ); } + +#[test] +fn actionctx_uninstalladdon_streams_bucket() { + #[derive(Model, Clone, Default)] + #[model(TestEnv)] + struct TestModel { + ctx: Ctx, + } + let addon = Descriptor { + manifest: Manifest { + id: "id".to_owned(), + version: Version::new(0, 0, 1), + name: "name".to_owned(), + contact_email: None, + description: None, + logo: None, + background: None, + types: vec![], + resources: vec![], + id_prefixes: None, + catalogs: vec![], + addon_catalogs: vec![], + behavior_hints: Default::default(), + }, + transport_url: Url::parse("https://transport_url").unwrap(), + flags: Default::default(), + }; + let profile = Profile { + addons: vec![addon.to_owned()], + ..Default::default() + }; + let _env_mutex = TestEnv::reset().expect("Should have exclusive lock to TestEnv"); + STORAGE.write().unwrap().insert( + PROFILE_STORAGE_KEY.to_owned(), + serde_json::to_string(&profile).unwrap(), + ); + + let stream = Stream { + source: StreamSource::Url { + url: "https://source_url".parse().unwrap(), + }, + name: None, + description: None, + thumbnail: None, + subtitles: vec![], + behavior_hints: StreamBehaviorHints::default(), + }; + + let streams_item_key = StreamsItemKey { + meta_id: "tt123456".to_owned(), + video_id: "tt123456:1:0".to_owned(), + }; + + let streams_item = StreamsItem { + stream, + r#type: "movie".to_owned(), + meta_id: "tt123456".to_owned(), + video_id: "tt123456:1:0".to_owned(), + meta_transport_url: "https://transport_url".parse().unwrap(), + stream_transport_url: "https://transport_url".parse().unwrap(), + mtime: TestEnv::now(), + }; + + let mut streams = StreamsBucket::default(); + streams.items.insert(streams_item_key.clone(), streams_item); + + let (runtime, _rx) = Runtime::::new( + TestModel { + ctx: Ctx::new( + profile, + LibraryBucket::default(), + streams, + NotificationsBucket::new::(None, vec![]), + ), + }, + vec![], + 1000, + ); + TestEnv::run(|| { + runtime.dispatch(RuntimeAction { + field: None, + action: Action::Ctx(ActionCtx::UninstallAddon(addon)), + }) + }); + assert!( + runtime.model().unwrap().ctx.profile.addons.is_empty(), + "addons updated successfully in memory" + ); + assert!( + STORAGE + .read() + .unwrap() + .get(PROFILE_STORAGE_KEY) + .map_or(false, |data| { + serde_json::from_str::(data) + .unwrap() + .addons + .is_empty() + }), + "addons updated successfully in storage" + ); + assert!( + REQUESTS.read().unwrap().is_empty(), + "No requests have been sent" + ); + assert!( + !runtime + .model() + .unwrap() + .ctx + .streams + .items + .contains_key(&streams_item_key), + "streams have been removed from bucket" + ); +} From 383749281e35ec2777952f8ad25f990139b47d0d Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 12 Oct 2023 16:25:58 +0200 Subject: [PATCH 3/4] test(uninstall_addons): improve test for streams bucket --- src/unit_tests/ctx/uninstall_addon.rs | 116 +++++++++++++++++--------- 1 file changed, 77 insertions(+), 39 deletions(-) diff --git a/src/unit_tests/ctx/uninstall_addon.rs b/src/unit_tests/ctx/uninstall_addon.rs index 0810c7331..5caa78b41 100644 --- a/src/unit_tests/ctx/uninstall_addon.rs +++ b/src/unit_tests/ctx/uninstall_addon.rs @@ -19,6 +19,59 @@ use std::any::Any; use stremio_derive::Model; use url::Url; +type Addon = Descriptor; + +impl Addon { + fn new(transport_url: &str) -> Self { + Self { + manifest: Manifest { + id: "id".to_owned(), + version: Version::new(0, 0, 1), + name: "name".to_owned(), + contact_email: None, + description: None, + logo: None, + background: None, + types: vec![], + resources: vec![], + id_prefixes: None, + catalogs: vec![], + addon_catalogs: vec![], + behavior_hints: Default::default(), + }, + transport_url: Url::parse(transport_url).unwrap(), + flags: Default::default(), + } + } +} + +type AddonStreamsItem = StreamsItem; + +impl AddonStreamsItem { + fn new(addon: &Addon) -> Self { + let stream = Stream { + source: StreamSource::Url { + url: "https://source_url".parse().unwrap(), + }, + name: None, + description: None, + thumbnail: None, + subtitles: vec![], + behavior_hints: StreamBehaviorHints::default(), + }; + + Self { + stream, + r#type: "movie".to_owned(), + meta_id: "tt123456".to_owned(), + video_id: "tt123456:1:0".to_owned(), + meta_transport_url: addon.transport_url.clone(), + stream_transport_url: addon.transport_url.clone(), + mtime: TestEnv::now(), + } + } +} + #[test] fn actionctx_uninstalladdon() { #[derive(Model, Clone, Default)] @@ -379,25 +432,10 @@ fn actionctx_uninstalladdon_streams_bucket() { struct TestModel { ctx: Ctx, } - let addon = Descriptor { - manifest: Manifest { - id: "id".to_owned(), - version: Version::new(0, 0, 1), - name: "name".to_owned(), - contact_email: None, - description: None, - logo: None, - background: None, - types: vec![], - resources: vec![], - id_prefixes: None, - catalogs: vec![], - addon_catalogs: vec![], - behavior_hints: Default::default(), - }, - transport_url: Url::parse("https://transport_url").unwrap(), - flags: Default::default(), - }; + + let addon = Addon::new("https://transport_url"); + let addon_2 = Addon::new("https://transport_url_2"); + let profile = Profile { addons: vec![addon.to_owned()], ..Default::default() @@ -408,34 +446,24 @@ fn actionctx_uninstalladdon_streams_bucket() { serde_json::to_string(&profile).unwrap(), ); - let stream = Stream { - source: StreamSource::Url { - url: "https://source_url".parse().unwrap(), - }, - name: None, - description: None, - thumbnail: None, - subtitles: vec![], - behavior_hints: StreamBehaviorHints::default(), - }; - let streams_item_key = StreamsItemKey { meta_id: "tt123456".to_owned(), video_id: "tt123456:1:0".to_owned(), }; - let streams_item = StreamsItem { - stream, - r#type: "movie".to_owned(), + let streams_item_key_2 = StreamsItemKey { meta_id: "tt123456".to_owned(), - video_id: "tt123456:1:0".to_owned(), - meta_transport_url: "https://transport_url".parse().unwrap(), - stream_transport_url: "https://transport_url".parse().unwrap(), - mtime: TestEnv::now(), + video_id: "tt123456:1:1".to_owned(), }; + let stream_item = AddonStreamsItem::new(&addon); + let stream_item_2 = AddonStreamsItem::new(&addon_2); + let mut streams = StreamsBucket::default(); - streams.items.insert(streams_item_key.clone(), streams_item); + streams.items.insert(streams_item_key.clone(), stream_item); + streams + .items + .insert(streams_item_key_2.clone(), stream_item_2); let (runtime, _rx) = Runtime::::new( TestModel { @@ -484,6 +512,16 @@ fn actionctx_uninstalladdon_streams_bucket() { .streams .items .contains_key(&streams_item_key), - "streams have been removed from bucket" + "stream item was removed from the bucket" + ); + assert!( + runtime + .model() + .unwrap() + .ctx + .streams + .items + .contains_key(&streams_item_key_2), + "stream item still is in the bucket" ); } From 63b5aee577c8766a70ebf50493593d271ca6f26a Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 12 Oct 2023 17:27:18 +0200 Subject: [PATCH 4/4] test(uninstall_addon): use basic function instead of implementing them --- src/unit_tests/ctx/uninstall_addon.rs | 92 ++++++++++++--------------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/src/unit_tests/ctx/uninstall_addon.rs b/src/unit_tests/ctx/uninstall_addon.rs index 5caa78b41..5c1f4a8e8 100644 --- a/src/unit_tests/ctx/uninstall_addon.rs +++ b/src/unit_tests/ctx/uninstall_addon.rs @@ -19,56 +19,48 @@ use std::any::Any; use stremio_derive::Model; use url::Url; -type Addon = Descriptor; - -impl Addon { - fn new(transport_url: &str) -> Self { - Self { - manifest: Manifest { - id: "id".to_owned(), - version: Version::new(0, 0, 1), - name: "name".to_owned(), - contact_email: None, - description: None, - logo: None, - background: None, - types: vec![], - resources: vec![], - id_prefixes: None, - catalogs: vec![], - addon_catalogs: vec![], - behavior_hints: Default::default(), - }, - transport_url: Url::parse(transport_url).unwrap(), - flags: Default::default(), - } +fn create_addon_descriptor(transport_url: &str) -> Descriptor { + Descriptor { + manifest: Manifest { + id: "id".to_owned(), + version: Version::new(0, 0, 1), + name: "name".to_owned(), + contact_email: None, + description: None, + logo: None, + background: None, + types: vec![], + resources: vec![], + id_prefixes: None, + catalogs: vec![], + addon_catalogs: vec![], + behavior_hints: Default::default(), + }, + transport_url: Url::parse(transport_url).unwrap(), + flags: Default::default(), } } -type AddonStreamsItem = StreamsItem; - -impl AddonStreamsItem { - fn new(addon: &Addon) -> Self { - let stream = Stream { - source: StreamSource::Url { - url: "https://source_url".parse().unwrap(), - }, - name: None, - description: None, - thumbnail: None, - subtitles: vec![], - behavior_hints: StreamBehaviorHints::default(), - }; +fn create_addon_streams_item(addon: &Descriptor) -> StreamsItem { + let stream = Stream { + source: StreamSource::Url { + url: "https://source_url".parse().unwrap(), + }, + name: None, + description: None, + thumbnail: None, + subtitles: vec![], + behavior_hints: StreamBehaviorHints::default(), + }; - Self { - stream, - r#type: "movie".to_owned(), - meta_id: "tt123456".to_owned(), - video_id: "tt123456:1:0".to_owned(), - meta_transport_url: addon.transport_url.clone(), - stream_transport_url: addon.transport_url.clone(), - mtime: TestEnv::now(), - } + StreamsItem { + stream, + r#type: "movie".to_owned(), + meta_id: "tt123456".to_owned(), + video_id: "tt123456:1:0".to_owned(), + meta_transport_url: addon.transport_url.clone(), + stream_transport_url: addon.transport_url.clone(), + mtime: TestEnv::now(), } } @@ -433,8 +425,8 @@ fn actionctx_uninstalladdon_streams_bucket() { ctx: Ctx, } - let addon = Addon::new("https://transport_url"); - let addon_2 = Addon::new("https://transport_url_2"); + let addon = create_addon_descriptor("https://transport_url"); + let addon_2 = create_addon_descriptor("https://transport_url_2"); let profile = Profile { addons: vec![addon.to_owned()], @@ -456,8 +448,8 @@ fn actionctx_uninstalladdon_streams_bucket() { video_id: "tt123456:1:1".to_owned(), }; - let stream_item = AddonStreamsItem::new(&addon); - let stream_item_2 = AddonStreamsItem::new(&addon_2); + let stream_item = create_addon_streams_item(&addon); + let stream_item_2 = create_addon_streams_item(&addon_2); let mut streams = StreamsBucket::default(); streams.items.insert(streams_item_key.clone(), stream_item);