Skip to content

Commit

Permalink
feat: Player - skip gaps API request
Browse files Browse the repository at this point in the history
Signed-off-by: Lachezar Lechev <[email protected]>
  • Loading branch information
elpiel committed Dec 7, 2023
1 parent 5ac29e1 commit 1d15379
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 3 deletions.
110 changes: 108 additions & 2 deletions src/models/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ use crate::runtime::msg::{Action, ActionLoad, ActionPlayer, Event, Internal, Msg
use crate::runtime::{Effect, EffectFuture, Effects, Env, EnvFutureExt, UpdateWithCtx};
use crate::types::addon::{AggrRequest, Descriptor, ExtraExt, ResourcePath, ResourceRequest};
use crate::types::api::{
fetch_api, APIRequest, APIResult, SeekLog, SeekLogRequest, SuccessResponse,
fetch_api, APIRequest, APIResult, SeekLog, SeekLogRequest, SkipGapsRequest, SkipGapsResponse,
SuccessResponse,
};
use crate::types::library::{LibraryBucket, LibraryItem};
use crate::types::profile::Settings as ProfileSettings;
use crate::types::profile::{Profile, Settings as ProfileSettings};
use crate::types::resource::{MetaItem, SeriesInfo, Stream, StreamSource, Subtitles, Video};
use crate::types::streams::{StreamItemState, StreamsBucket, StreamsItemKey};

Expand Down Expand Up @@ -97,6 +98,7 @@ pub struct Player {
pub series_info: Option<SeriesInfo>,
pub library_item: Option<LibraryItem>,
pub stream_state: Option<StreamItemState>,
pub skip_gaps: Option<Loadable<SkipGapsResponse, CtxError>>,
#[serde(skip_serializing)]
pub watched: Option<WatchedBitField>,
#[serde(skip_serializing)]
Expand Down Expand Up @@ -197,6 +199,15 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
let watched_effects =
watched_update(&mut self.watched, &self.meta_item, &self.library_item);

let skip_gaps_effects = skip_gaps_update::<E>(
&ctx.profile,
self.selected.as_ref(),
self.video_params.as_ref(),
self.series_info.as_ref(),
self.library_item.as_ref(),
&mut self.skip_gaps,
);

// dismiss LibraryItem notification if we have a LibraryItem to begin with
let notification_effects = match &self.library_item {
Some(library_item) => Effects::msg(Msg::Internal(
Expand Down Expand Up @@ -245,6 +256,7 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
.join(series_info_effects)
.join(library_item_effects)
.join(watched_effects)
.join(skip_gaps_effects)
.join(notification_effects)
}
Msg::Action(Action::Unload) => {
Expand Down Expand Up @@ -287,6 +299,7 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
let series_info_effects = eq_update(&mut self.series_info, None);
let library_item_effects = eq_update(&mut self.library_item, None);
let watched_effects = eq_update(&mut self.watched, None);
let skip_gaps_effects = eq_update(&mut self.skip_gaps, None);
self.analytics_context = None;
self.load_time = None;
self.loaded = false;
Expand All @@ -307,6 +320,7 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
.join(series_info_effects)
.join(library_item_effects)
.join(watched_effects)
.join(skip_gaps_effects)
.join(ended_effects)
}
Msg::Action(Action::Player(ActionPlayer::VideoParamsChanged { video_params })) => {
Expand Down Expand Up @@ -619,6 +633,14 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
.join(library_item_effects)
.join(watched_effects)
}
Msg::Internal(Internal::SkipGapsResult(_skip_gaps_request, result)) => {
let skip_gaps_next = match result.to_owned() {
Ok(response) => Loadable::Ready(response),
Err(err) => Loadable::Err(err),
};

eq_update(&mut self.skip_gaps, Some(skip_gaps_next))
}
Msg::Internal(Internal::ProfileChanged) => {
if let Some(analytics_context) = &mut self.analytics_context {
analytics_context.has_trakt = ctx.profile.has_trakt::<E>();
Expand Down Expand Up @@ -1042,6 +1064,90 @@ fn push_seek_to_api<E: Env + 'static>(seek_log_req: SeekLogRequest) -> Effect {
.into()
}

fn skip_gaps_update<E: Env + 'static>(
profile: &Profile,
selected: Option<&Selected>,
video_params: Option<&VideoParams>,
series_info: Option<&SeriesInfo>,
library_item: Option<&LibraryItem>,
skip_gaps: &mut Option<Loadable<SkipGapsResponse, CtxError>>,
) -> Effects {
let active_premium = profile.auth.as_ref().and_then(|auth| {
auth.user
.premium_expire
.filter(|premium_expire| premium_expire > &E::now())
});

let skip_gaps_request_effects = match (
active_premium,
selected,
video_params,
series_info,
library_item,
) {
(
Some(_expires),
Some(selected),
Some(video_params),
Some(series_info),
Some(library_item),
) => {
match (
&selected.stream.source,
selected.stream.name.as_ref(),
video_params.hash.clone(),
) {
(StreamSource::Torrent { .. }, Some(stream_name), Some(opensubtitles_hash)) => {
let stream_name_hash = {
use sha2::Digest;
let mut sha256 = sha2::Sha256::new();
sha256.update(stream_name);
let sha256_encoded = sha256.finalize();

BASE64.encode(sha256_encoded)
};

let skip_gaps_request = SkipGapsRequest {
opensubtitles_hash,
item_id: library_item.id.to_owned(),
series_info: series_info.to_owned(),
stream_name_hash,
};
let skip_gaps_request_effects = get_skip_gaps::<E>(skip_gaps_request);

let skip_gaps_effects = eq_update(skip_gaps, Some(Loadable::Loading));

Effects::one(skip_gaps_request_effects)
.unchanged()
.join(skip_gaps_effects)
}
_ => Effects::none().unchanged(),
}
}
_ => Effects::none().unchanged(),
};

skip_gaps_request_effects
}

fn get_skip_gaps<E: Env + 'static>(skip_gaps_request: SkipGapsRequest) -> Effect {
let api_request = APIRequest::SkipGaps(skip_gaps_request.clone());

EffectFuture::Concurrent(
fetch_api::<E, _, _, SkipGapsResponse>(&api_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: Result<SkipGapsResponse, CtxError>| {
Msg::Internal(Internal::SkipGapsResult(skip_gaps_request, result))
})
.boxed_env(),
)
.into()
}

#[cfg(test)]
mod test {
use chrono::{TimeZone, Utc};
Expand Down
4 changes: 3 additions & 1 deletion src/runtime/msg/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::runtime::EnvError;
use crate::types::addon::{Descriptor, Manifest, ResourceRequest, ResourceResponse};
use crate::types::api::{
APIRequest, AuthRequest, DataExportResponse, DatastoreRequest, LinkCodeResponse,
LinkDataResponse, SeekLogRequest, SuccessResponse,
LinkDataResponse, SeekLogRequest, SkipGapsRequest, SkipGapsResponse, SuccessResponse,
};
use crate::types::library::{LibraryBucket, LibraryItem, LibraryItemId};
use crate::types::profile::{Auth, AuthKey, Profile, User};
Expand Down Expand Up @@ -116,6 +116,8 @@ pub enum Internal {
///
/// Applicable only to movie series and torrents.
SeekLogsResult(SeekLogRequest, Result<SuccessResponse, CtxError>),
/// Retrieve the skip gaps for skipping intro and outro.
SkipGapsResult(SkipGapsRequest, Result<SkipGapsResponse, CtxError>),
/// The result of querying the data for LocalSearch
LoadLocalSearchResult(Url, Result<Vec<Searchable>, EnvError>),
}
49 changes: 49 additions & 0 deletions src/types/api/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,12 @@ pub enum APIRequest {
auth_key: AuthKey,
events: Vec<serde_json::Value>,
},
/// Sends Seek log request to the API
#[serde(rename_all = "camelCase")]
SeekLog(SeekLogRequest),
/// Retrieve Skip gaps for Intro and Outro in movie series from the API
#[serde(rename_all = "camelCase")]
SkipGaps(SkipGapsRequest),
}

#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
Expand Down Expand Up @@ -87,6 +91,50 @@ pub struct SeekLogRequest {
pub skip_outro: Vec<u64>,
}

#[derive(Clone, PartialEq, Eq, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SkipGapsRequest {
/// Opensubtitles hash returned by the server
#[serde(rename = "osId")]
pub opensubtitles_hash: String,
pub item_id: String,
#[serde(flatten)]
pub series_info: SeriesInfo,
/// Stream name hash
///
/// base64 encoded SHA-256 hash of the Stream file name.
#[serde(rename = "stHash")]
pub stream_name_hash: String,
}

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SkipGapsResponse {
/// Returns the matched attribute: Opensubtitles Hash, File name or season with episode
///
/// Primarily used for debugging
pub accuracy: String,
pub gaps: Vec<SkipGaps>,
}

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SkipGaps {
pub seek_history: Vec<SeekEvent>,
pub outro: Option<u64>,
}

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SeekEvent {
pub insert_id: u64,
// pub rank: u64,
#[serde(rename = "seekFrom")]
pub from: u64,
#[serde(rename = "seekTo")]
pub to: u64,
}

impl FetchRequestParams<APIRequest> for APIRequest {
fn endpoint(&self) -> Url {
API_URL.to_owned()
Expand All @@ -107,6 +155,7 @@ impl FetchRequestParams<APIRequest> for APIRequest {
APIRequest::DataExport { .. } => "dataExport".to_owned(),
APIRequest::Events { .. } => "events".to_owned(),
APIRequest::SeekLog { .. } => "seekLog".to_owned(),
APIRequest::SkipGaps { .. } => "getSkipGaps".to_owned(),
}
}
fn query(&self) -> Option<String> {
Expand Down

0 comments on commit 1d15379

Please sign in to comment.