Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove stream from bucket upon addon uninstall #535

Merged
merged 4 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/models/ctx/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ impl<E: Env + 'static> Update<E> for Ctx {
Some(auth_key) => Effects::one(delete_session::<E>(auth_key)).unchanged(),
_ => Effects::none().unchanged(),
};
let profile_effects = update_profile::<E>(&mut self.profile, &self.status, msg);
let profile_effects =
update_profile::<E>(&mut self.profile, &mut self.streams, &self.status, msg);
let library_effects =
update_library::<E>(&mut self.library, &self.profile, &self.status, msg);
let streams_effects = update_streams::<E>(&mut self.streams, &self.status, msg);
Expand Down Expand Up @@ -110,7 +111,8 @@ impl<E: Env + 'static> Update<E> for Ctx {
.join(notifications_effects)
}
Msg::Internal(Internal::CtxAuthResult(auth_request, result)) => {
let profile_effects = update_profile::<E>(&mut self.profile, &self.status, msg);
let profile_effects =
update_profile::<E>(&mut self.profile, &mut self.streams, &self.status, msg);
let library_effects =
update_library::<E>(&mut self.library, &self.profile, &self.status, msg);
let trakt_addon_effects = update_trakt_addon::<E>(
Expand Down Expand Up @@ -157,7 +159,8 @@ impl<E: Env + 'static> Update<E> for Ctx {
.join(ctx_effects)
}
_ => {
let profile_effects = update_profile::<E>(&mut self.profile, &self.status, msg);
let profile_effects =
update_profile::<E>(&mut self.profile, &mut self.streams, &self.status, msg);
let library_effects =
update_library::<E>(&mut self.library, &self.profile, &self.status, msg);
let streams_effects = update_streams::<E>(&mut self.streams, &self.status, msg);
Expand Down
8 changes: 8 additions & 0 deletions src/models/ctx/update_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<E: Env + 'static>(
profile: &mut Profile,
streams: &mut StreamsBucket,
status: &CtxStatus,
msg: &Msg,
) -> Effects {
Expand Down Expand Up @@ -159,6 +161,12 @@ pub fn update_profile<E: Env + 'static>(
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::<E>(
profile.addons.to_owned(),
Expand Down
149 changes: 148 additions & 1 deletion src/unit_tests/ctx/uninstall_addon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -18,6 +19,51 @@ use std::any::Any;
use stremio_derive::Model;
use url::Url;

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(),
}
}

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(),
};

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(),
}
}

#[test]
fn actionctx_uninstalladdon() {
#[derive(Model, Clone, Default)]
Expand Down Expand Up @@ -370,3 +416,104 @@ 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 = create_addon_descriptor("https://transport_url");
let addon_2 = create_addon_descriptor("https://transport_url_2");

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 streams_item_key = StreamsItemKey {
meta_id: "tt123456".to_owned(),
video_id: "tt123456:1:0".to_owned(),
};

let streams_item_key_2 = StreamsItemKey {
meta_id: "tt123456".to_owned(),
video_id: "tt123456:1:1".to_owned(),
};

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);
streams
.items
.insert(streams_item_key_2.clone(), stream_item_2);

let (runtime, _rx) = Runtime::<TestEnv, _>::new(
TestModel {
ctx: Ctx::new(
profile,
LibraryBucket::default(),
streams,
NotificationsBucket::new::<TestEnv>(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::<Profile>(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),
"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"
);
}