Skip to content

Commit

Permalink
Merge pull request #621 from Stremio/fix/user-unable-to-login-if-addo…
Browse files Browse the repository at this point in the history
…n-collection-fetch-fails

fix: Addon collection and Library fetch on user login raise flags
  • Loading branch information
elpiel authored Jan 31, 2024
2 parents 4811436 + 856e503 commit 19ff6d6
Show file tree
Hide file tree
Showing 12 changed files with 327 additions and 130 deletions.
2 changes: 1 addition & 1 deletion src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub const NOTIFICATION_ITEMS_COUNT: usize = 100;
pub const WATCHED_THRESHOLD_COEF: f64 = 0.7;
pub const CREDITS_THRESHOLD_COEF: f64 = 0.9;
/// The latest migration scheme version
pub const SCHEMA_VERSION: u32 = 12;
pub const SCHEMA_VERSION: u32 = 13;
pub const IMDB_LINK_CATEGORY: &str = "imdb";
pub const GENRES_LINK_CATEGORY: &str = "Genres";
pub const CINEMETA_TOP_CATALOG_ID: &str = "top";
Expand Down
178 changes: 117 additions & 61 deletions src/models/ctx/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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 All @@ -24,7 +24,9 @@ use enclose::enclose;
use futures::{future, FutureExt, TryFutureExt};
use serde::Serialize;

use tracing::{event, trace, Level};
use tracing::{error, trace};

use super::OtherError;

#[derive(Default, PartialEq, Eq, Serialize, Clone, Debug)]
pub enum CtxStatus {
Expand Down Expand Up @@ -165,10 +167,50 @@ impl<E: Env + 'static> Update<E> for Ctx {
{
self.status = CtxStatus::Ready;
match result {
Ok(_) => Effects::msg(Msg::Event(Event::UserAuthenticated {
auth_request: auth_request.to_owned(),
}))
.unchanged(),
Ok(ctx_auth) => {
let authentication_effects =
Effects::msg(Msg::Event(Event::UserAuthenticated {
auth_request: auth_request.to_owned(),
}))
.unchanged();

let addons_locked_event = Event::UserAddonsLocked {
addons_locked: ctx_auth.addons_result.is_err(),
};

let addons_effects = match ctx_auth.addons_result.as_ref() {
Ok(_addons) => {
Effects::msg(Msg::Event(addons_locked_event)).unchanged()
}
Err(_err) => Effects::msg(Msg::Event(Event::Error {
error: CtxError::Other(OtherError::UserAddonsAreLocked),
source: Box::new(addons_locked_event),
}))
.unchanged(),
};

let library_missing_event = Event::UserLibraryMissing {
library_missing: ctx_auth.library_items_result.is_err(),
};

let library_missing_effects = match ctx_auth
.library_items_result
.as_ref()
{
Ok(_library) => {
Effects::msg(Msg::Event(library_missing_event)).unchanged()
}
Err(_err) => Effects::msg(Msg::Event(Event::Error {
error: CtxError::Other(OtherError::UserLibraryIsMissing),
source: Box::new(library_missing_event),
}))
.unchanged(),
};

authentication_effects
.join(addons_effects)
.join(library_missing_effects)
}
Err(error) => Effects::msg(Msg::Event(Event::Error {
error: error.to_owned(),
source: Box::new(Event::UserAuthenticated {
Expand Down Expand Up @@ -229,66 +271,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 = async {
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")
})
.await
.map_err(CtxError::from)
.and_then(|result: APIResult<CollectionResponse>| match result {
APIResult::Ok { result } => Ok(result),
APIResult::Err { error } => Err(CtxError::from(error)),
})
.map(|CollectionResponse { addons, .. }| addons)
};

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));
let datastore_library_fut = async {
let request = DatastoreRequest {
auth_key: auth.key.to_owned(),
collection: LIBRARY_COLLECTION_NAME.to_owned(),
command: DatastoreCommand::Get {
ids: vec![],
all: true,
},
};

event!(Level::TRACE, internal_message = ?internal_msg);
internal_msg
}))
.boxed_env(),
fetch_api::<E, _, _, LibraryItemsResponse>(&request)
.inspect(move |result| {
trace!(?result, ?request, "Get user's Addon Collection request")
})
.await
.map_err(CtxError::from)
.and_then(|result| match result {
APIResult::Ok { result } => Ok(result.0),
APIResult::Err { error } => Err(CtxError::from(error)),
})
};

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

if let Err(error) = addon_collection_result.as_ref() {
error!("Failed to fetch Addon collection from API: {error:?}");
}
if let Err(error) = datastore_library_result.as_ref() {
error!("Failed to fetch LibraryItems for user from API: {error:?}");
}

Ok(CtxAuthResponse {
auth,
addons_result: addon_collection_result,
library_items_result: datastore_library_result,
})
}
.map(enclose!((auth_request) move |result| {
Msg::Internal(Internal::CtxAuthResult(auth_request, result))
}))
.boxed_env(),
)
.into()
}
Expand Down
6 changes: 6 additions & 0 deletions src/models/ctx/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pub enum OtherError {
AddonNotInstalled,
AddonIsProtected,
AddonConfigurationRequired,
UserAddonsAreLocked,
UserLibraryIsMissing,
}

impl OtherError {
Expand All @@ -49,6 +51,8 @@ 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 => "Fetching Addons from the API failed and we have defaulted the addons to the officials ones until the request succeeds".to_owned(),
OtherError::UserLibraryIsMissing => "Fetching Library from the API failed and we have defaulted to empty library until the request succeeds".to_owned(),
}
}
pub fn code(&self) -> u64 {
Expand All @@ -59,6 +63,8 @@ impl OtherError {
OtherError::AddonNotInstalled => 4,
OtherError::AddonIsProtected => 5,
OtherError::AddonConfigurationRequired => 6,
OtherError::UserAddonsAreLocked => 7,
OtherError::UserLibraryIsMissing => 8,
}
}
}
Expand Down
44 changes: 30 additions & 14 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 Down Expand Up @@ -175,11 +175,18 @@ 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 =>
{
let next_library =
LibraryBucket::new(Some(auth.user.id.to_owned()), library_items.to_owned());
(
CtxStatus::Loading(loading_auth_request),
Ok(CtxAuthResponse {
auth,
library_items_result,
..
}),
) if loading_auth_request == auth_request => {
let next_library = LibraryBucket::new(
Some(auth.user.id.to_owned()),
library_items_result.to_owned().unwrap_or_default(),
);
if *library != next_library {
*library = next_library;
Effects::msg(Msg::Internal(Internal::LibraryChanged(false)))
Expand Down Expand Up @@ -243,14 +250,23 @@ 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) => {
// send an event that the missing library is now present
let library_missing_effects = Effects::msg(Msg::Event(Event::UserLibraryMissing {
library_missing: false,
}))
.unchanged();

library_missing_effects
.join(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
Loading

0 comments on commit 19ff6d6

Please sign in to comment.