Skip to content

Commit

Permalink
Merge pull request #585 from Stremio/feat/implement-ctx-events
Browse files Browse the repository at this point in the history
feat: implement events for ctx
  • Loading branch information
tymmesyde authored Dec 12, 2023
2 parents 8af7cf0 + 4d1943a commit 15e540e
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 7 deletions.
18 changes: 13 additions & 5 deletions src/models/ctx/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use crate::constants::LIBRARY_COLLECTION_NAME;
use crate::models::common::{DescriptorLoadable, ResourceLoadable};
use crate::models::common::{DescriptorLoadable, Loadable, ResourceLoadable};
use crate::models::ctx::{
update_library, update_notifications, update_profile, update_search_history, update_streams,
update_trakt_addon, CtxError,
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::{Effect, EffectFuture, Effects, Env, EnvFutureExt, Update};
use crate::types::api::{
fetch_api, APIRequest, APIResult, AuthRequest, AuthResponse, CollectionResponse,
DatastoreCommand, DatastoreRequest, LibraryItemsResponse, SuccessResponse,
};
use crate::types::events::Events;
use crate::types::library::LibraryBucket;
use crate::types::notifications::NotificationsBucket;
use crate::types::profile::{Auth, AuthKey, Profile};
Expand All @@ -21,7 +22,7 @@ use crate::types::streams::StreamsBucket;
use derivative::Derivative;
use enclose::enclose;
use futures::{future, FutureExt, TryFutureExt};
use serde::{Deserialize, Serialize};
use serde::Serialize;

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

Expand All @@ -32,7 +33,7 @@ pub enum CtxStatus {
Ready,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Clone, Debug)]
#[cfg_attr(test, derive(Derivative))]
#[cfg_attr(test, derivative(Default))]
pub struct Ctx {
Expand All @@ -54,6 +55,7 @@ pub struct Ctx {
pub trakt_addon: Option<DescriptorLoadable>,
#[serde(skip)]
pub notification_catalogs: Vec<ResourceLoadable<Vec<MetaItem>>>,
pub events: Events,
}

impl Ctx {
Expand All @@ -73,6 +75,10 @@ impl Ctx {
trakt_addon: None,
notification_catalogs: vec![],
status: CtxStatus::Ready,
events: Events {
modal: Loadable::Loading,
notification: Loadable::Loading,
},
}
}
}
Expand Down Expand Up @@ -195,12 +201,14 @@ impl<E: Env + 'static> Update<E> for Ctx {
);
let search_history_effects =
update_search_history::<E>(&mut self.search_history, &self.status, msg);
let events_effects = update_events::<E>(&mut self.events, msg);
profile_effects
.join(library_effects)
.join(streams_effects)
.join(trakt_addon_effects)
.join(notifications_effects)
.join(search_history_effects)
.join(events_effects)
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/models/ctx/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod update_events;
use update_events::*;

mod update_library;
use update_library::*;

Expand Down
73 changes: 73 additions & 0 deletions src/models/ctx/update_events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use chrono::DateTime;
use futures::{future, FutureExt, TryFutureExt};

use crate::models::common::{eq_update, Loadable};
use crate::models::ctx::CtxError;
use crate::runtime::msg::{Action, ActionCtx, Internal, Msg};
use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt};
use crate::types::api::{
fetch_api, APIRequest, APIResult, GetModalResponse, GetNotificationResponse,
};
use crate::types::events::Events;

pub fn update_events<E: Env + 'static>(events: &mut Events, msg: &Msg) -> Effects {
match msg {
Msg::Action(Action::Ctx(ActionCtx::GetEvents)) => {
let modal_effects = eq_update(&mut events.modal, Loadable::Loading);
let notification_effects = eq_update(&mut events.notification, Loadable::Loading);
let requests_effects = Effects::many(vec![get_modal::<E>(), get_notification::<E>()]);

modal_effects
.join(notification_effects)
.join(requests_effects)
}
Msg::Internal(Internal::GetModalResult(_, result)) => match result {
Ok(response) => eq_update(&mut events.modal, Loadable::Ready(response.to_owned())),
Err(error) => eq_update(&mut events.modal, Loadable::Err(error.to_owned())),
},
Msg::Internal(Internal::GetNotificationResult(_, result)) => match result {
Ok(response) => eq_update(
&mut events.notification,
Loadable::Ready(response.to_owned()),
),
Err(error) => eq_update(&mut events.notification, Loadable::Err(error.to_owned())),
},
_ => Effects::none().unchanged(),
}
}

fn get_modal<E: Env + 'static>() -> Effect {
let request = APIRequest::GetModal {
date: DateTime::from(E::now()),
};

EffectFuture::Concurrent(
fetch_api::<E, _, _, Option<GetModalResponse>>(&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(move |result| Msg::Internal(Internal::GetModalResult(request, result)))
.boxed_env(),
)
.into()
}

fn get_notification<E: Env + 'static>() -> Effect {
let request = APIRequest::GetNotification {
date: DateTime::from(E::now()),
};

EffectFuture::Concurrent(
fetch_api::<E, _, _, Option<GetNotificationResponse>>(&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(move |result| Msg::Internal(Internal::GetNotificationResult(request, result)))
.boxed_env(),
)
.into()
}
2 changes: 2 additions & 0 deletions src/runtime/msg/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ pub enum ActionCtx {
///
/// [`LibraryItem`]: crate::types::library::LibraryItem
PullNotifications,
/// Make request to api to get events modal and notification
GetEvents,
}

#[derive(Clone, Deserialize, Debug)]
Expand Down
11 changes: 9 additions & 2 deletions src/runtime/msg/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::models::streaming_server::{
use crate::runtime::EnvError;
use crate::types::addon::{Descriptor, Manifest, ResourceRequest, ResourceResponse};
use crate::types::api::{
APIRequest, AuthRequest, DataExportResponse, DatastoreRequest, LinkCodeResponse,
LinkDataResponse, SeekLogRequest, SuccessResponse,
APIRequest, AuthRequest, DataExportResponse, DatastoreRequest, GetModalResponse,
GetNotificationResponse, LinkCodeResponse, LinkDataResponse, SeekLogRequest, SuccessResponse,
};
use crate::types::library::{LibraryBucket, LibraryItem, LibraryItemId};
use crate::types::profile::{Auth, AuthKey, Profile, User};
Expand Down Expand Up @@ -124,4 +124,11 @@ pub enum Internal {
SeekLogsResult(SeekLogRequest, Result<SuccessResponse, CtxError>),
/// The result of querying the data for LocalSearch
LoadLocalSearchResult(Url, Result<Vec<Searchable>, EnvError>),
/// Result for getModal request
GetModalResult(APIRequest, Result<Option<GetModalResponse>, CtxError>),
/// Result for getNotification request
GetNotificationResult(
APIRequest,
Result<Option<GetNotificationResponse>, CtxError>,
),
}
11 changes: 11 additions & 0 deletions src/types/api/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::types::addon::Descriptor;
use crate::types::library::LibraryItem;
use crate::types::profile::{AuthKey, GDPRConsent, User};
use crate::types::resource::SeriesInfo;
use chrono::{DateTime, Local};
#[cfg(test)]
use derivative::Derivative;
use http::Method;
Expand Down Expand Up @@ -56,6 +57,14 @@ pub enum APIRequest {
},
#[serde(rename_all = "camelCase")]
SeekLog(SeekLogRequest),
#[serde(rename_all = "camelCase")]
GetModal {
date: DateTime<Local>,
},
#[serde(rename_all = "camelCase")]
GetNotification {
date: DateTime<Local>,
},
}

#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
Expand Down Expand Up @@ -107,6 +116,8 @@ impl FetchRequestParams<APIRequest> for APIRequest {
APIRequest::DataExport { .. } => "dataExport".to_owned(),
APIRequest::Events { .. } => "events".to_owned(),
APIRequest::SeekLog { .. } => "seekLog".to_owned(),
APIRequest::GetModal { .. } => "getModal".to_owned(),
APIRequest::GetNotification { .. } => "getNotification".to_owned(),
}
}
fn query(&self) -> Option<String> {
Expand Down
28 changes: 28 additions & 0 deletions src/types/api/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use chrono::{serde::ts_milliseconds, DateTime, Utc};
use derive_more::TryInto;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use url::Url;

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
Expand Down Expand Up @@ -74,6 +75,33 @@ pub enum LinkDataResponse {
AuthKey(LinkAuthKey),
}

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ModalAddon {
pub name: String,
pub manifest_url: Url,
}

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GetModalResponse {
pub id: String,
pub title: String,
pub message: String,
pub image_url: Url,
pub addon: Option<ModalAddon>,
pub external_url: Option<Url>,
}

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GetNotificationResponse {
pub id: String,
pub title: String,
pub message: String,
pub external_url: Option<Url>,
}

/// API response for the [`LibraryItem`]s which skips invalid items
/// when deserializing.
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
Expand Down
12 changes: 12 additions & 0 deletions src/types/events/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use serde::Serialize;

use crate::{
models::{common::Loadable, ctx::CtxError},
types::api::{GetModalResponse, GetNotificationResponse},
};

#[derive(Default, PartialEq, Eq, Serialize, Clone, Debug)]
pub struct Events {
pub modal: Loadable<Option<GetModalResponse>, CtxError>,
pub notification: Loadable<Option<GetNotificationResponse>, CtxError>,
}
2 changes: 2 additions & 0 deletions src/types/events/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod events;
pub use events::*;
1 change: 1 addition & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod addon;
pub mod api;
pub mod events;
pub mod library;
pub mod notifications;
pub mod profile;
Expand Down
98 changes: 98 additions & 0 deletions src/unit_tests/ctx/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use std::any::Any;

use futures::future;
use stremio_derive::Model;

use crate::{
models::ctx::Ctx,
runtime::{
msg::{Action, ActionCtx},
EnvFutureExt, Runtime, RuntimeAction, TryEnvFuture,
},
types::api::{APIResult, GetModalResponse, GetNotificationResponse},
unit_tests::{default_fetch_handler, Request, TestEnv, FETCH_HANDLER},
};

#[test]
fn test_events() {
#[derive(Model, Clone, Debug)]
#[model(TestEnv)]
struct TestModel {
ctx: Ctx,
}

fn fetch_handler(request: Request) -> TryEnvFuture<Box<dyn Any + Send>> {
match request {
Request { url, .. } if url == "https://api.strem.io/api/getModal" => {
future::ok(Box::new(APIResult::Ok {
result: Some(GetModalResponse {
id: "id".to_owned(),
title: "title".to_owned(),
message: "message".to_owned(),
image_url: "https://image_url".parse().unwrap(),
addon: None,
external_url: None,
}),
}) as Box<dyn Any + Send>)
.boxed_env()
}
Request { url, .. } if url == "https://api.strem.io/api/getNotification" => {
future::ok(Box::new(APIResult::Ok {
result: Some(GetNotificationResponse {
id: "id".to_owned(),
title: "title".to_owned(),
message: "message".to_owned(),
external_url: None,
}),
}) as Box<dyn Any + Send>)
.boxed_env()
}
_ => default_fetch_handler(request),
}
}

let _env_mutex = TestEnv::reset().expect("Should have exclusive lock to TestEnv");

*FETCH_HANDLER.write().unwrap() = Box::new(fetch_handler);

let (runtime, _rx) = Runtime::<TestEnv, _>::new(
TestModel {
ctx: Ctx::default(),
},
vec![],
1000,
);

assert!(
runtime.model().unwrap().ctx.events.modal.is_loading(),
"Modal should be loading"
);

assert!(
runtime
.model()
.unwrap()
.ctx
.events
.notification
.is_loading(),
"Notification should be loading"
);

TestEnv::run(|| {
runtime.dispatch(RuntimeAction {
field: None,
action: Action::Ctx(ActionCtx::GetEvents),
})
});

assert!(
runtime.model().unwrap().ctx.events.modal.is_ready(),
"Modal should be ready"
);

assert!(
runtime.model().unwrap().ctx.events.notification.is_ready(),
"Notification should be ready"
);
}
1 change: 1 addition & 0 deletions src/unit_tests/ctx/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod add_to_library;
mod authenticate;
mod events;
mod install_addon;
mod logout;
mod notifications {
Expand Down

0 comments on commit 15e540e

Please sign in to comment.