Skip to content

Commit

Permalink
fix: Addon collection and Library fetch on user login raise flags
Browse files Browse the repository at this point in the history
Signed-off-by: Lachezar Lechev <[email protected]>
  • Loading branch information
elpiel committed Jan 18, 2024
1 parent 9b0b073 commit c553cda
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 82 deletions.
132 changes: 73 additions & 59 deletions src/models/ctx/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::constants::LIBRARY_COLLECTION_NAME;
use crate::constants::{LIBRARY_COLLECTION_NAME, OFFICIAL_ADDONS};
use crate::models::common::{DescriptorLoadable, Loadable, ResourceLoadable};
use crate::models::ctx::{
update_events, update_library, update_notifications, update_profile, update_search_history,
update_streams, update_trakt_addon, CtxError,
};
use crate::runtime::msg::{Action, ActionCtx, Event, Internal, Msg};
use crate::runtime::msg::{Action, ActionCtx, CtxAuthResponse, Event, Internal, Msg};
use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt, Update};
use crate::types::api::{
fetch_api, APIRequest, APIResult, AuthRequest, AuthResponse, CollectionResponse,
Expand Down Expand Up @@ -103,7 +103,7 @@ impl<E: Env + 'static> Update<E> for Ctx {
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);
update_library::<E>(&mut self.library, &mut self.profile, &self.status, msg);
let streams_effects = update_streams::<E>(&mut self.streams, &self.status, msg);
let search_history_effects =
update_search_history::<E>(&mut self.search_history, &self.status, msg);
Expand Down Expand Up @@ -139,7 +139,7 @@ impl<E: Env + 'static> Update<E> for Ctx {
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);
update_library::<E>(&mut self.library, &mut self.profile, &self.status, msg);
let trakt_addon_effects = update_trakt_addon::<E>(
&mut self.trakt_addon,
&self.profile,
Expand Down Expand Up @@ -193,7 +193,7 @@ impl<E: Env + 'static> Update<E> for Ctx {
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);
update_library::<E>(&mut self.library, &mut self.profile, &self.status, msg);
let streams_effects = update_streams::<E>(&mut self.streams, &self.status, msg);
let trakt_addon_effects = update_trakt_addon::<E>(
&mut self.trakt_addon,
Expand Down Expand Up @@ -229,66 +229,80 @@ fn authenticate<E: Env + 'static>(auth_request: &AuthRequest) -> Effect {
let auth_api = APIRequest::Auth(auth_request.clone());

EffectFuture::Concurrent(
E::flush_analytics()
.then(move |_| {
fetch_api::<E, _, _, _>(&auth_api)
.inspect(move |result| trace!(?result, ?auth_api, "Auth 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(|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::<E, _, _, _>(&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)
};
async {
E::flush_analytics().await;

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,
},
};
// return an error only if the auth request fails
let auth = fetch_api::<E, _, _, _>(&auth_api)
.inspect(move |result| trace!(?result, ?auth_api, "Auth request"))
.await
.map_err(CtxError::from)
.and_then(|result| match result {
APIResult::Ok { result } => Ok(result),
APIResult::Err { error } => Err(CtxError::from(error)),
})
.map(|AuthResponse { key, user }| Auth { key, user })?;

fetch_api::<E, _, _, LibraryItemsResponse>(&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)),
})
let addon_collection_fut = {
let request = APIRequest::AddonCollectionGet {
auth_key: auth.key.to_owned(),
update: true,
};
fetch_api::<E, _, _, _>(&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)
};

future::try_join(addon_collection_fut, datastore_library_fut)
.map_ok(move |(addons, library_items)| (auth, addons, library_items))
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::<E, _, _, LibraryItemsResponse>(&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)),
})
};

let (addon_collection_result, datastore_library_result) =
future::join(addon_collection_fut, datastore_library_fut).await;

// lock if the result from fetching the addons has failed
let addons_locked = addon_collection_result.is_err();
// set the flag to true if fetching of the library has failed
let library_missing = datastore_library_result.is_err();
Ok(CtxAuthResponse {
auth,
addons: addon_collection_result.unwrap_or(OFFICIAL_ADDONS.clone()),
addons_locked,
library_items: datastore_library_result.unwrap_or_default(),
library_missing,
})
.map(enclose!((auth_request) move |result| {
let internal_msg = Msg::Internal(Internal::CtxAuthResult(auth_request, result));
}
.map(enclose!((auth_request) move |result| {
let internal_msg = Msg::Internal(Internal::CtxAuthResult(auth_request, result));

event!(Level::TRACE, internal_message = ?internal_msg);
internal_msg
}))
.boxed_env(),
event!(Level::TRACE, internal_message = ?internal_msg);
internal_msg
}))
.boxed_env(),
)
.into()
}
Expand Down
3 changes: 3 additions & 0 deletions src/models/ctx/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub enum OtherError {
AddonNotInstalled,
AddonIsProtected,
AddonConfigurationRequired,
UserAddonsAreLocked,
}

impl OtherError {
Expand All @@ -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::UserAddonsAreLocked => "Syncing Addon from the API failed and we have defaulted the addons to the officials ones until the request succeeds".to_owned(),
}
}
pub fn code(&self) -> u64 {
Expand All @@ -59,6 +61,7 @@ impl OtherError {
OtherError::AddonNotInstalled => 4,
OtherError::AddonIsProtected => 5,
OtherError::AddonConfigurationRequired => 6,
OtherError::UserAddonsAreLocked => 7,
}
}
}
Expand Down
36 changes: 23 additions & 13 deletions src/models/ctx/update_library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
},
models::ctx::{CtxError, CtxStatus, OtherError},
runtime::{
msg::{Action, ActionCtx, Event, Internal, Msg},
msg::{Action, ActionCtx, CtxAuthResponse, Event, Internal, Msg},
Effect, EffectFuture, Effects, Env, EnvFutureExt,
},
types::{
Expand All @@ -27,7 +27,7 @@ use crate::{

pub fn update_library<E: Env + 'static>(
library: &mut LibraryBucket,
profile: &Profile,
profile: &mut Profile,
status: &CtxStatus,
msg: &Msg,
) -> Effects {
Expand Down Expand Up @@ -175,9 +175,14 @@ pub fn update_library<E: Env + 'static>(
Effects::one(push_library_to_storage::<E>(library)).unchanged()
}
Msg::Internal(Internal::CtxAuthResult(auth_request, result)) => match (status, result) {
(CtxStatus::Loading(loading_auth_request), Ok((auth, _, library_items)))
if loading_auth_request == auth_request =>
{
(
CtxStatus::Loading(loading_auth_request),
Ok(CtxAuthResponse {
auth,
library_items,
..
}),
) 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 {
Expand Down Expand Up @@ -243,14 +248,19 @@ pub fn update_library<E: Env + 'static>(
},
result,
)) if Some(loading_auth_key) == auth_key => match result {
Ok(items) => Effects::msg(Msg::Event(Event::LibraryItemsPulledFromAPI {
ids: ids.to_owned(),
}))
.join(Effects::one(update_and_push_items_to_storage::<E>(
library,
items.to_owned(),
)))
.join(Effects::msg(Msg::Internal(Internal::LibraryChanged(true)))),
Ok(items) => {
// override the missing library flag to indicate that we've successfully updated the local library
profile.library_missing = false;

Effects::msg(Msg::Event(Event::LibraryItemsPulledFromAPI {
ids: ids.to_owned(),
}))
.join(Effects::one(update_and_push_items_to_storage::<E>(
library,
items.to_owned(),
)))
.join(Effects::msg(Msg::Internal(Internal::LibraryChanged(true))))
}
Err(error) => Effects::msg(Msg::Event(Event::Error {
error: error.to_owned(),
source: Box::new(Event::LibraryItemsPulledFromAPI {
Expand Down
28 changes: 24 additions & 4 deletions src/models/ctx/update_profile.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::constants::{OFFICIAL_ADDONS, PROFILE_STORAGE_KEY};
use crate::models::ctx::{CtxError, CtxStatus, OtherError};
use crate::runtime::msg::{Action, ActionCtx, Event, Internal, Msg};
use crate::runtime::msg::{Action, ActionCtx, CtxAuthResponse, Event, Internal, Msg};
use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt};
use crate::types::addon::Descriptor;
use crate::types::api::{
Expand Down Expand Up @@ -153,6 +153,10 @@ pub fn update_profile<E: Env + 'static>(
.join(Effects::msg(Msg::Internal(Internal::ProfileChanged)))
}
Msg::Internal(Internal::UninstallAddon(addon)) => {
if profile.addons_locked {
return addon_install_error_effects(addon, OtherError::UserAddonsAreLocked);
}

let addon_position = profile
.addons
.iter()
Expand Down Expand Up @@ -229,6 +233,10 @@ pub fn update_profile<E: Env + 'static>(
Effects::one(push_profile_to_storage::<E>(profile)).unchanged()
}
Msg::Internal(Internal::InstallAddon(addon)) => {
if profile.addons_locked {
return addon_install_error_effects(addon, OtherError::UserAddonsAreLocked);
}

if !profile.addons.contains(addon) {
if !addon.manifest.behavior_hints.configuration_required {
let addon_position = profile
Expand Down Expand Up @@ -263,12 +271,21 @@ pub fn update_profile<E: Env + 'static>(
}
}
Msg::Internal(Internal::CtxAuthResult(auth_request, result)) => match (status, result) {
(CtxStatus::Loading(loading_auth_request), Ok((auth, addons, _)))
if loading_auth_request == auth_request =>
{
(
CtxStatus::Loading(loading_auth_request),
Ok(CtxAuthResponse {
auth,
addons,
addons_locked,
library_missing,
..
}),
) if loading_auth_request == auth_request => {
let next_profile = Profile {
auth: Some(auth.to_owned()),
addons: addons.to_owned(),
addons_locked: *addons_locked,
library_missing: *library_missing,
settings: Settings::default(),
};
if *profile != next_profile {
Expand All @@ -285,6 +302,9 @@ pub fn update_profile<E: Env + 'static>(
result,
)) if profile.auth_key() == Some(auth_key) => match result {
Ok(addons) => {
// on successful AddonsApi result, unlock the addons if they have been locked
profile.addons_locked = false;

let prev_transport_urls = profile
.addons
.iter()
Expand Down
4 changes: 2 additions & 2 deletions src/models/ctx/update_search_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use futures::FutureExt;

use crate::constants::SEARCH_HISTORY_STORAGE_KEY;
use crate::models::ctx::{CtxError, CtxStatus};
use crate::runtime::msg::{Action, ActionCtx, Event, Internal};
use crate::runtime::msg::{Action, ActionCtx, CtxAuthResponse, Event, Internal};
use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt};
use crate::{runtime::msg::Msg, types::search_history::SearchHistoryBucket};

Expand All @@ -27,7 +27,7 @@ pub fn update_search_history<E: Env + 'static>(
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(CtxAuthResponse { auth, .. }))
if loading_auth_request == auth_request =>
{
let next_search_history = SearchHistoryBucket::new(Some(auth.user.id.to_owned()));
Expand Down
4 changes: 2 additions & 2 deletions src/models/ctx/update_streams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::collections::hash_map::Entry;
use crate::constants::STREAMS_STORAGE_KEY;
use crate::models::common::{Loadable, ResourceLoadable};
use crate::models::ctx::{CtxError, CtxStatus};
use crate::runtime::msg::{Action, ActionCtx, Event, Internal, Msg};
use crate::runtime::msg::{Action, ActionCtx, CtxAuthResponse, Event, Internal, Msg};
use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt};
use crate::types::streams::{StreamsBucket, StreamsItem, StreamsItemKey};

Expand Down Expand Up @@ -85,7 +85,7 @@ pub fn update_streams<E: Env + 'static>(
Effects::one(push_streams_to_storage::<E>(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(CtxAuthResponse { auth, .. }))
if loading_auth_request == auth_request =>
{
let next_streams = StreamsBucket::new(Some(auth.user.id.to_owned()));
Expand Down
16 changes: 14 additions & 2 deletions src/runtime/msg/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,19 @@ pub type CtxStorageResponse = (
Option<LibraryBucket>,
);

pub type AuthResponse = (Auth, Vec<Descriptor>, Vec<LibraryItem>);
#[derive(Debug)]
pub struct CtxAuthResponse {
pub auth: Auth,
pub addons: Vec<Descriptor>,
/// If the addon get request fails, this flag will be `true`
/// to disallow the user to install addons and override his addons in the API
pub addons_locked: bool,
pub library_items: Vec<LibraryItem>,
/// If the Library datastore Get request fails on initial logging
/// this flag will be set to `true` to indicate why the user doesn't see
/// their library items.
pub library_missing: bool,
}

pub type LibraryPlanResponse = (Vec<String>, Vec<String>);

Expand All @@ -33,7 +45,7 @@ pub type LibraryPlanResponse = (Vec<String>, Vec<String>);
#[derive(Debug)]
pub enum Internal {
/// Result for authenticate to API.
CtxAuthResult(AuthRequest, Result<AuthResponse, CtxError>),
CtxAuthResult(AuthRequest, Result<CtxAuthResponse, CtxError>),
/// Result for pull addons from API.
AddonsAPIResult(APIRequest, Result<Vec<Descriptor>, CtxError>),
/// Result for pull user from API.
Expand Down
Loading

0 comments on commit c553cda

Please sign in to comment.