From b803d89202e6ca63fe6ee39b8740c0655bde8d1a Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 27 Jan 2024 08:42:46 +0100 Subject: [PATCH 1/5] refactor: move pull library and addons out of ctx --- src/models/ctx/ctx.rs | 46 +------------------------ src/models/ctx/update_library.rs | 32 ++++++++--------- src/models/ctx/update_profile.rs | 28 ++++++--------- src/models/ctx/update_search_history.rs | 2 +- src/models/ctx/update_streams.rs | 2 +- src/runtime/msg/internal.rs | 2 +- src/unit_tests/ctx/authenticate.rs | 30 +++++----------- 7 files changed, 37 insertions(+), 105 deletions(-) diff --git a/src/models/ctx/ctx.rs b/src/models/ctx/ctx.rs index b30d0402b..555edba2f 100644 --- a/src/models/ctx/ctx.rs +++ b/src/models/ctx/ctx.rs @@ -1,4 +1,3 @@ -use crate::constants::LIBRARY_COLLECTION_NAME; use crate::models::common::{DescriptorLoadable, Loadable, ResourceLoadable}; use crate::models::ctx::{ update_events, update_library, update_notifications, update_profile, update_search_history, @@ -7,8 +6,7 @@ use crate::models::ctx::{ use crate::runtime::msg::{Action, ActionCtx, Event, Internal, Msg}; use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt, Update}; use crate::types::api::{ - fetch_api, APIRequest, APIResult, AuthRequest, AuthResponse, CollectionResponse, - DatastoreCommand, DatastoreRequest, LibraryItemsResponse, SuccessResponse, + fetch_api, APIRequest, APIResult, AuthRequest, AuthResponse, SuccessResponse, }; use crate::types::events::{DismissedEventsBucket, Events}; use crate::types::library::LibraryBucket; @@ -240,48 +238,6 @@ fn authenticate(auth_request: &AuthRequest) -> Effect { APIResult::Err { error } => future::err(CtxError::from(error)), }) .map_ok(|AuthResponse { key, user }| Auth { key, user }) - .and_then(|auth| { - let addon_collection_fut = { - let request = APIRequest::AddonCollectionGet { - auth_key: auth.key.to_owned(), - update: true, - }; - fetch_api::(&request) - .inspect(move |result| { - trace!(?result, ?request, "Get user's Addon Collection request") - }) - .map_err(CtxError::from) - .and_then(|result| match result { - APIResult::Ok { result } => future::ok(result), - APIResult::Err { error } => future::err(CtxError::from(error)), - }) - .map_ok(|CollectionResponse { addons, .. }| addons) - }; - - let datastore_library_fut = { - let request = DatastoreRequest { - auth_key: auth.key.to_owned(), - collection: LIBRARY_COLLECTION_NAME.to_owned(), - command: DatastoreCommand::Get { - ids: vec![], - all: true, - }, - }; - - fetch_api::(&request) - .inspect(move |result| { - trace!(?result, ?request, "Get user's Addon Collection request") - }) - .map_err(CtxError::from) - .and_then(|result| match result { - APIResult::Ok { result } => future::ok(result.0), - APIResult::Err { error } => future::err(CtxError::from(error)), - }) - }; - - future::try_join(addon_collection_fut, datastore_library_fut) - .map_ok(move |(addons, library_items)| (auth, addons, library_items)) - }) .map(enclose!((auth_request) move |result| { let internal_msg = Msg::Internal(Internal::CtxAuthResult(auth_request, result)); diff --git a/src/models/ctx/update_library.rs b/src/models/ctx/update_library.rs index 7de922d5c..a00fda558 100644 --- a/src/models/ctx/update_library.rs +++ b/src/models/ctx/update_library.rs @@ -34,13 +34,8 @@ pub fn update_library( let auth_key = profile.auth_key(); match msg { Msg::Action(Action::Ctx(ActionCtx::Logout)) | Msg::Internal(Internal::Logout) => { - let next_library = LibraryBucket::default(); - if *library != next_library { - *library = next_library; - Effects::msg(Msg::Internal(Internal::LibraryChanged(false))) - } else { - Effects::none().unchanged() - } + *library = LibraryBucket::default(); + Effects::msg(Msg::Internal(Internal::LibraryChanged(false))) } Msg::Action(Action::Ctx(ActionCtx::AddToLibrary(meta_preview))) => { let mut library_item = match library.items.get(&meta_preview.id) { @@ -175,17 +170,13 @@ pub fn update_library( Effects::one(push_library_to_storage::(library)).unchanged() } Msg::Internal(Internal::CtxAuthResult(auth_request, result)) => match (status, result) { - (CtxStatus::Loading(loading_auth_request), Ok((auth, _, library_items))) + (CtxStatus::Loading(loading_auth_request), Ok(auth)) if loading_auth_request == auth_request => { - let next_library = - LibraryBucket::new(Some(auth.user.id.to_owned()), library_items.to_owned()); - if *library != next_library { - *library = next_library; - Effects::msg(Msg::Internal(Internal::LibraryChanged(false))) - } else { - Effects::none().unchanged() - } + *library = LibraryBucket::new(Some(auth.user.id.to_owned()), vec![]); + let changed_effects = Effects::msg(Msg::Internal(Internal::LibraryChanged(false))); + let pull_effects = Effects::one(pull_items_from_api::(vec![], true, &auth.key)); + changed_effects.join(pull_effects) } _ => Effects::none().unchanged(), }, @@ -214,6 +205,7 @@ pub fn update_library( } else { Effects::one(pull_items_from_api::( pull_ids.to_owned(), + false, loading_auth_key, )) .unchanged() @@ -370,11 +362,15 @@ fn push_items_to_api(items: Vec, auth_key: &AuthK .into() } -fn pull_items_from_api(ids: Vec, auth_key: &AuthKey) -> Effect { +fn pull_items_from_api( + ids: Vec, + all: bool, + auth_key: &AuthKey, +) -> Effect { let request = DatastoreRequest { auth_key: auth_key.to_owned(), collection: LIBRARY_COLLECTION_NAME.to_owned(), - command: DatastoreCommand::Get { ids, all: false }, + command: DatastoreCommand::Get { ids, all }, }; EffectFuture::Concurrent( fetch_api::(&request) diff --git a/src/models/ctx/update_profile.rs b/src/models/ctx/update_profile.rs index e04c73c13..2c826dda8 100644 --- a/src/models/ctx/update_profile.rs +++ b/src/models/ctx/update_profile.rs @@ -6,7 +6,7 @@ use crate::types::addon::Descriptor; use crate::types::api::{ fetch_api, APIError, APIRequest, APIResult, CollectionResponse, SuccessResponse, }; -use crate::types::profile::{Auth, AuthKey, Profile, Settings, User}; +use crate::types::profile::{Auth, AuthKey, Profile, User}; use crate::types::streams::StreamsBucket; use enclose::enclose; use futures::{future, FutureExt, TryFutureExt}; @@ -20,13 +20,8 @@ pub fn update_profile( ) -> Effects { match msg { Msg::Action(Action::Ctx(ActionCtx::Logout)) | Msg::Internal(Internal::Logout) => { - let next_profile = Profile::default(); - if *profile != next_profile { - *profile = next_profile; - Effects::msg(Msg::Internal(Internal::ProfileChanged)) - } else { - Effects::none().unchanged() - } + *profile = Profile::default(); + Effects::msg(Msg::Internal(Internal::ProfileChanged)) } Msg::Action(Action::Ctx(ActionCtx::PushUserToAPI)) => match &profile.auth { Some(Auth { key, user }) => { @@ -263,20 +258,17 @@ pub fn update_profile( } } Msg::Internal(Internal::CtxAuthResult(auth_request, result)) => match (status, result) { - (CtxStatus::Loading(loading_auth_request), Ok((auth, addons, _))) + (CtxStatus::Loading(loading_auth_request), Ok(auth)) if loading_auth_request == auth_request => { - let next_profile = Profile { + *profile = Profile { auth: Some(auth.to_owned()), - addons: addons.to_owned(), - settings: Settings::default(), + ..Default::default() }; - if *profile != next_profile { - *profile = next_profile; - Effects::msg(Msg::Internal(Internal::ProfileChanged)) - } else { - Effects::none().unchanged() - } + + let changed_effects = Effects::msg(Msg::Internal(Internal::ProfileChanged)); + let pull_effects = Effects::one(pull_addons_from_api::(&auth.key)); + changed_effects.join(pull_effects) } _ => Effects::none().unchanged(), }, diff --git a/src/models/ctx/update_search_history.rs b/src/models/ctx/update_search_history.rs index f6cb746ce..cda7a021a 100644 --- a/src/models/ctx/update_search_history.rs +++ b/src/models/ctx/update_search_history.rs @@ -27,7 +27,7 @@ pub fn update_search_history( Effects::msg(Msg::Internal(Internal::SearchHistoryChanged)) } Msg::Internal(Internal::CtxAuthResult(auth_request, result)) => match (status, result) { - (CtxStatus::Loading(loading_auth_request), Ok((auth, ..))) + (CtxStatus::Loading(loading_auth_request), Ok(auth)) if loading_auth_request == auth_request => { let next_search_history = SearchHistoryBucket::new(Some(auth.user.id.to_owned())); diff --git a/src/models/ctx/update_streams.rs b/src/models/ctx/update_streams.rs index b3b9cdeee..d712ff596 100644 --- a/src/models/ctx/update_streams.rs +++ b/src/models/ctx/update_streams.rs @@ -85,7 +85,7 @@ pub fn update_streams( 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, _, _))) + (CtxStatus::Loading(loading_auth_request), Ok(auth)) if loading_auth_request == auth_request => { let next_streams = StreamsBucket::new(Some(auth.user.id.to_owned())); diff --git a/src/runtime/msg/internal.rs b/src/runtime/msg/internal.rs index 7fd43dbbe..e99661180 100644 --- a/src/runtime/msg/internal.rs +++ b/src/runtime/msg/internal.rs @@ -23,7 +23,7 @@ pub type CtxStorageResponse = ( Option, ); -pub type AuthResponse = (Auth, Vec, Vec); +pub type AuthResponse = Auth; pub type LibraryPlanResponse = (Vec, Vec); diff --git a/src/unit_tests/ctx/authenticate.rs b/src/unit_tests/ctx/authenticate.rs index 2d8f68455..cc3fdbbad 100644 --- a/src/unit_tests/ctx/authenticate.rs +++ b/src/unit_tests/ctx/authenticate.rs @@ -180,13 +180,9 @@ fn actionctx_authenticate_login() { LibraryBucket::new(Some("user_id".to_owned()), vec![]), "recent library updated successfully in storage" ); - assert_eq!( - serde_json::from_str::( - STORAGE.read().unwrap().get(LIBRARY_STORAGE_KEY).unwrap() - ) - .unwrap(), - LibraryBucket::new(Some("user_id".to_owned()), vec![]), - "library updated successfully in storage" + assert!( + STORAGE.read().unwrap().get(LIBRARY_STORAGE_KEY).is_none(), + "Library slot updated successfully in storage" ); assert_eq!( REQUESTS.read().unwrap().len(), @@ -385,13 +381,9 @@ fn actionctx_authenticate_login_with_token() { LibraryBucket::new(Some("user_id".to_owned()), vec![]), "recent library updated successfully in storage" ); - assert_eq!( - serde_json::from_str::( - STORAGE.read().unwrap().get(LIBRARY_STORAGE_KEY).unwrap() - ) - .unwrap(), - LibraryBucket::new(Some("user_id".to_owned()), vec![]), - "library updated successfully in storage" + assert!( + STORAGE.read().unwrap().get(LIBRARY_STORAGE_KEY).is_none(), + "Library slot updated successfully in storage" ); assert_eq!( REQUESTS.read().unwrap().len(), @@ -598,13 +590,9 @@ fn actionctx_authenticate_register() { LibraryBucket::new(Some("user_id".to_owned()), vec![]), "recent library updated successfully in storage" ); - assert_eq!( - serde_json::from_str::( - STORAGE.read().unwrap().get(LIBRARY_STORAGE_KEY).unwrap() - ) - .unwrap(), - LibraryBucket::new(Some("user_id".to_owned()), vec![]), - "library updated successfully in storage" + assert!( + STORAGE.read().unwrap().get(LIBRARY_STORAGE_KEY).is_none(), + "Library slot updated successfully in storage" ); assert_eq!( REQUESTS.read().unwrap().len(), From b9a7d25159b6e9ab9a586f94f7e6fdde68806a13 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 30 Jan 2024 09:54:57 +0100 Subject: [PATCH 2/5] fix: lock addons when pull failed --- src/models/ctx/update_profile.rs | 25 +++++++++++++++---------- src/types/profile/profile.rs | 3 +++ src/unit_tests/serde/profile.rs | 3 +++ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/models/ctx/update_profile.rs b/src/models/ctx/update_profile.rs index 2c826dda8..7f6672dbd 100644 --- a/src/models/ctx/update_profile.rs +++ b/src/models/ctx/update_profile.rs @@ -107,13 +107,13 @@ pub fn update_profile( } } }, - Msg::Action(Action::Ctx(ActionCtx::InstallAddon(addon))) => { + Msg::Action(Action::Ctx(ActionCtx::InstallAddon(addon))) if !profile.addons_locked => { Effects::msg(Msg::Internal(Internal::InstallAddon(addon.to_owned()))).unchanged() } - Msg::Action(Action::Ctx(ActionCtx::UninstallAddon(addon))) => { + Msg::Action(Action::Ctx(ActionCtx::UninstallAddon(addon))) if !profile.addons_locked => { Effects::msg(Msg::Internal(Internal::UninstallAddon(addon.to_owned()))).unchanged() } - Msg::Action(Action::Ctx(ActionCtx::UpgradeAddon(addon))) => { + Msg::Action(Action::Ctx(ActionCtx::UpgradeAddon(addon))) if !profile.addons_locked => { if profile.addons.contains(addon) { return addon_upgrade_error_effects(addon, OtherError::AddonAlreadyInstalled); } @@ -294,6 +294,8 @@ pub fn update_profile( .into_iter() .chain(removed_transport_urls) .collect(); + + profile.addons_locked = false; if profile.addons != *addons { profile.addons = addons.to_owned(); Effects::msg(Msg::Event(Event::AddonsPulledFromAPI { transport_urls })) @@ -303,13 +305,16 @@ pub fn update_profile( .unchanged() } } - Err(error) => Effects::msg(Msg::Event(Event::Error { - error: error.to_owned(), - source: Box::new(Event::AddonsPulledFromAPI { - transport_urls: Default::default(), - }), - })) - .unchanged(), + Err(error) => { + profile.addons_locked = true; + Effects::msg(Msg::Event(Event::Error { + error: error.to_owned(), + source: Box::new(Event::AddonsPulledFromAPI { + transport_urls: Default::default(), + }), + })) + .unchanged() + } }, Msg::Internal(Internal::UserAPIResult(APIRequest::GetUser { auth_key }, result)) if profile.auth_key() == Some(auth_key) => diff --git a/src/types/profile/profile.rs b/src/types/profile/profile.rs index 8e6b46cd6..dccfd7ca8 100644 --- a/src/types/profile/profile.rs +++ b/src/types/profile/profile.rs @@ -19,6 +19,8 @@ pub struct Profile { pub auth: Option, #[serde_as(deserialize_as = "UniqueVec, DescriptorUniqueVecAdapter>")] pub addons: Vec, + #[serde(skip)] + pub addons_locked: bool, pub settings: Settings, } @@ -27,6 +29,7 @@ impl Default for Profile { Profile { auth: None, addons: OFFICIAL_ADDONS.to_owned(), + addons_locked: false, settings: Settings::default(), } } diff --git a/src/unit_tests/serde/profile.rs b/src/unit_tests/serde/profile.rs index 86e5742cc..07572a398 100644 --- a/src/unit_tests/serde/profile.rs +++ b/src/unit_tests/serde/profile.rs @@ -9,11 +9,13 @@ fn profile() { Profile { auth: Some(Auth::default()), addons: vec![], + addons_locked: false, settings: Settings::default(), }, Profile { auth: None, addons: vec![], + addons_locked: false, settings: Settings::default(), }, ], @@ -57,6 +59,7 @@ fn profile() { &Profile { auth: None, addons: vec![], + addons_locked: false, settings: Settings::default(), }, &[ From 92cfc667250657f26ad2c7bb5d1749c5c7293339 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 30 Jan 2024 10:27:41 +0100 Subject: [PATCH 3/5] test: add install addon with lock --- src/unit_tests/ctx/install_addon.rs | 105 +++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/src/unit_tests/ctx/install_addon.rs b/src/unit_tests/ctx/install_addon.rs index 4e1e7f1e2..d286f4fdb 100644 --- a/src/unit_tests/ctx/install_addon.rs +++ b/src/unit_tests/ctx/install_addon.rs @@ -3,7 +3,7 @@ use crate::models::ctx::Ctx; use crate::runtime::msg::{Action, ActionCtx}; use crate::runtime::{Env, EnvFutureExt, Runtime, RuntimeAction, TryEnvFuture}; use crate::types::addon::{Descriptor, Manifest}; -use crate::types::api::{APIResult, SuccessResponse}; +use crate::types::api::{APIError, APIResult, CollectionResponse, SuccessResponse}; use crate::types::events::DismissedEventsBucket; use crate::types::library::LibraryBucket; use crate::types::notifications::NotificationsBucket; @@ -396,3 +396,106 @@ fn actionctx_installaddon_already_installed() { "No requests have been sent" ); } + +#[test] +fn actionctx_installaddon_install_with_user_and_pull_addons_failed() { + #[derive(Model, Clone, Default)] + #[model(TestEnv)] + struct TestModel { + ctx: Ctx, + } + fn fetch_handler(request: Request) -> TryEnvFuture> { + match request { + Request { + url, method, body, .. + } if url == "https://api.strem.io/api/addonCollectionGet" + && method == "POST" + && body == "{\"type\":\"AddonCollectionGet\",\"authKey\":\"auth_key\",\"update\":true}" => + { + future::ok(Box::new(APIResult::::Err { + error: APIError::default(), + }) as Box).boxed_env() + } + _ => default_fetch_handler(request), + } + } + 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 _env_mutex = TestEnv::reset().expect("Should have exclusive lock to TestEnv"); + *FETCH_HANDLER.write().unwrap() = Box::new(fetch_handler); + let (runtime, _rx) = Runtime::::new( + TestModel { + ctx: Ctx::new( + Profile { + auth: Some(Auth { + key: AuthKey("auth_key".to_owned()), + user: User { + id: "user_id".to_owned(), + email: "user_email".to_owned(), + fb_id: None, + avatar: None, + last_modified: TestEnv::now(), + date_registered: TestEnv::now(), + trakt: None, + premium_expire: None, + gdpr_consent: GDPRConsent { + tos: true, + privacy: true, + marketing: true, + from: Some("tests".to_owned()), + }, + }, + }), + addons: vec![], + ..Default::default() + }, + LibraryBucket::default(), + StreamsBucket::default(), + NotificationsBucket::new::(None, vec![]), + SearchHistoryBucket::default(), + DismissedEventsBucket::default(), + ), + }, + vec![], + 1000, + ); + TestEnv::run(|| { + runtime.dispatch(RuntimeAction { + field: None, + action: Action::Ctx(ActionCtx::PullAddonsFromAPI), + }) + }); + TestEnv::run(|| { + runtime.dispatch(RuntimeAction { + field: None, + action: Action::Ctx(ActionCtx::InstallAddon(addon.to_owned())), + }) + }); + assert_eq!( + runtime.model().unwrap().ctx.profile.addons, + vec![], + "addon should not be installed" + ); + assert_eq!( + REQUESTS.read().unwrap().len(), + 1, + "One request has been sent" + ); +} From 348273f5c69b32619e284113a3595441578fb8ae Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 30 Jan 2024 10:35:50 +0100 Subject: [PATCH 4/5] refactor(ctx): return error event if addons are locked --- src/models/ctx/error.rs | 3 + src/models/ctx/update_profile.rs | 190 ++++++++++++++++--------------- 2 files changed, 104 insertions(+), 89 deletions(-) diff --git a/src/models/ctx/error.rs b/src/models/ctx/error.rs index 175c825bb..098044fb2 100644 --- a/src/models/ctx/error.rs +++ b/src/models/ctx/error.rs @@ -38,6 +38,7 @@ pub enum OtherError { AddonNotInstalled, AddonIsProtected, AddonConfigurationRequired, + AddonsAreLocked, } impl OtherError { @@ -49,6 +50,7 @@ impl OtherError { OtherError::AddonNotInstalled => "Addon is not installed".to_owned(), OtherError::AddonIsProtected => "Addon is protected".to_owned(), OtherError::AddonConfigurationRequired => "Addon requires configuration".to_owned(), + OtherError::AddonsAreLocked => "Addons are locked".to_owned(), } } pub fn code(&self) -> u64 { @@ -59,6 +61,7 @@ impl OtherError { OtherError::AddonNotInstalled => 4, OtherError::AddonIsProtected => 5, OtherError::AddonConfigurationRequired => 6, + OtherError::AddonsAreLocked => 7, } } } diff --git a/src/models/ctx/update_profile.rs b/src/models/ctx/update_profile.rs index 7f6672dbd..37cc60496 100644 --- a/src/models/ctx/update_profile.rs +++ b/src/models/ctx/update_profile.rs @@ -107,80 +107,88 @@ pub fn update_profile( } } }, - Msg::Action(Action::Ctx(ActionCtx::InstallAddon(addon))) if !profile.addons_locked => { + Msg::Action(Action::Ctx(ActionCtx::InstallAddon(addon))) => { Effects::msg(Msg::Internal(Internal::InstallAddon(addon.to_owned()))).unchanged() } - Msg::Action(Action::Ctx(ActionCtx::UninstallAddon(addon))) if !profile.addons_locked => { + Msg::Action(Action::Ctx(ActionCtx::UninstallAddon(addon))) => { Effects::msg(Msg::Internal(Internal::UninstallAddon(addon.to_owned()))).unchanged() } - Msg::Action(Action::Ctx(ActionCtx::UpgradeAddon(addon))) if !profile.addons_locked => { - 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() + Msg::Action(Action::Ctx(ActionCtx::UpgradeAddon(addon))) => { + if !profile.addons_locked { + if profile.addons.contains(addon) { + return addon_upgrade_error_effects(addon, OtherError::AddonAlreadyInstalled); } - _ => 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))) + 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))) + } else { + addon_upgrade_error_effects(addon, OtherError::AddonsAreLocked) + } } Msg::Internal(Internal::UninstallAddon(addon)) => { - let addon_position = profile - .addons - .iter() - .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 && !addon.flags.protected { - profile.addons.remove(addon_position); + if !profile.addons_locked { + let addon_position = profile + .addons + .iter() + .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 && !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); + // 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(), - auth_key, - )) - .unchanged(), - _ => Effects::none().unchanged(), - }; - Effects::msg(Msg::Event(Event::AddonUninstalled { - 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))) + 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::AddonUninstalled { + 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 { + addon_uninstall_error_effects(addon, OtherError::AddonIsProtected) + } } else { - addon_uninstall_error_effects(addon, OtherError::AddonIsProtected) + addon_uninstall_error_effects(addon, OtherError::AddonNotInstalled) } } else { - addon_uninstall_error_effects(addon, OtherError::AddonNotInstalled) + addon_uninstall_error_effects(addon, OtherError::AddonsAreLocked) } } Msg::Action(Action::Ctx(ActionCtx::LogoutTrakt)) => match &mut profile.auth { @@ -224,37 +232,41 @@ pub fn update_profile( Effects::one(push_profile_to_storage::(profile)).unchanged() } Msg::Internal(Internal::InstallAddon(addon)) => { - if !profile.addons.contains(addon) { - if !addon.manifest.behavior_hints.configuration_required { - let addon_position = profile - .addons - .iter() - .map(|addon| &addon.transport_url) - .position(|transport_url| *transport_url == addon.transport_url); - if let Some(addon_position) = addon_position { - profile.addons[addon_position] = addon.to_owned(); + if !profile.addons_locked { + if !profile.addons.contains(addon) { + if !addon.manifest.behavior_hints.configuration_required { + let addon_position = profile + .addons + .iter() + .map(|addon| &addon.transport_url) + .position(|transport_url| *transport_url == addon.transport_url); + if let Some(addon_position) = addon_position { + profile.addons[addon_position] = addon.to_owned(); + } else { + 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::AddonInstalled { + 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 { - 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::AddonInstalled { - 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))) + addon_install_error_effects(addon, OtherError::AddonConfigurationRequired) + } } else { - addon_install_error_effects(addon, OtherError::AddonConfigurationRequired) + addon_install_error_effects(addon, OtherError::AddonAlreadyInstalled) } } else { - addon_install_error_effects(addon, OtherError::AddonAlreadyInstalled) + addon_install_error_effects(addon, OtherError::AddonsAreLocked) } } Msg::Internal(Internal::CtxAuthResult(auth_request, result)) => match (status, result) { From 3447035a09d39cd156a8d055576f0cce3cf8141c Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 30 Jan 2024 10:40:58 +0100 Subject: [PATCH 5/5] fix: fmt errors --- src/models/ctx/update_profile.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/models/ctx/update_profile.rs b/src/models/ctx/update_profile.rs index 37cc60496..176523eab 100644 --- a/src/models/ctx/update_profile.rs +++ b/src/models/ctx/update_profile.rs @@ -119,7 +119,10 @@ pub fn update_profile( return addon_upgrade_error_effects(addon, OtherError::AddonAlreadyInstalled); } if addon.manifest.behavior_hints.configuration_required { - return addon_upgrade_error_effects(addon, OtherError::AddonConfigurationRequired); + return addon_upgrade_error_effects( + addon, + OtherError::AddonConfigurationRequired, + ); } let addon_position = match profile .addons @@ -128,7 +131,9 @@ pub fn update_profile( .position(|transport_url| *transport_url == addon.transport_url) { Some(addon_position) => addon_position, - None => return addon_upgrade_error_effects(addon, OtherError::AddonNotInstalled), + 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);