public string AuthorizeDevice(TraktUser traktUser) { var deviceCodeRequest = new { client_id = TraktUris.ClientId }; TraktDeviceCode deviceCode; using (var response = PostToTrakt(TraktUris.DeviceCode, deviceCodeRequest, null)) { deviceCode = _jsonSerializer.DeserializeFromStream <TraktDeviceCode>(response.Result); } // Start polling in the background Plugin.Instance.PollingTasks[traktUser.LinkedMbUserId] = Task.Run(() => PollForAccessToken(deviceCode, traktUser)); return(deviceCode.user_code); }
public async Task RefreshUserAccessToken(TraktUser traktUser) { if (string.IsNullOrWhiteSpace(traktUser.RefreshToken)) { _logger.LogError("Tried to reauthenticate with Trakt, but no refreshToken was available"); return; } var data = new TraktUserRefreshTokenRequest { client_id = TraktUris.ClientId, client_secret = TraktUris.ClientSecret, redirect_uri = "urn:ietf:wg:oauth:2.0:oob", refresh_token = traktUser.RefreshToken, grant_type = "refresh_token" }; TraktUserAccessToken userAccessToken; try { using (var response = await PostToTrakt(TraktUris.AccessToken, data).ConfigureAwait(false)) { await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); userAccessToken = await JsonSerializer.DeserializeAsync <TraktUserAccessToken>(stream, _jsonOptions).ConfigureAwait(false); } } catch (HttpRequestException ex) { _logger.LogError(ex, "An error occurred during token refresh"); return; } if (userAccessToken != null) { traktUser.AccessToken = userAccessToken.access_token; traktUser.RefreshToken = userAccessToken.refresh_token; traktUser.AccessTokenExpiration = DateTime.Now.AddSeconds(userAccessToken.expirationWithBuffer); Plugin.Instance.SaveConfiguration(); _logger.LogInformation("Successfully refreshed the access token for user {UserId}", traktUser.LinkedMbUserId); } }
public async Task <string> AuthorizeDevice(TraktUser traktUser) { var deviceCodeRequest = new { client_id = TraktUris.ClientId }; TraktDeviceCode deviceCode; using (var response = await PostToTrakt(TraktUris.DeviceCode, deviceCodeRequest, null).ConfigureAwait(false)) { deviceCode = await JsonSerializer.DeserializeAsync <TraktDeviceCode>(response, _jsonOptions).ConfigureAwait(false); } // Start polling in the background Plugin.Instance.PollingTasks[traktUser.LinkedMbUserId] = Task.Run(() => PollForAccessToken(deviceCode, traktUser)); return(deviceCode.user_code); }
/// <summary> /// Add or remove a Show(Series) to/from the users trakt.tv library /// </summary> /// <param name="show">The show to remove</param> /// <param name="traktUser">The user who's library is being updated</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="eventType"></param> /// <returns>Task{TraktResponseDataContract}.</returns> public async Task <TraktSyncResponse> SendLibraryUpdateAsync(Series show, TraktUser traktUser, CancellationToken cancellationToken, EventType eventType) { if (show == null) { throw new ArgumentNullException("show"); } if (traktUser == null) { throw new ArgumentNullException("traktUser"); } if (eventType == EventType.Update) { return(null); } var showPayload = new List <TraktShowCollected> { new TraktShowCollected { title = show.Name, year = show.ProductionYear, ids = new TraktShowId { tvdb = show.GetProviderId(MetadataProviders.Tvdb).ConvertToInt(), imdb = show.GetProviderId(MetadataProviders.Imdb), tvrage = show.GetProviderId(MetadataProviders.TvRage).ConvertToInt() }, } }; var data = new TraktSyncCollected { shows = showPayload.ToList() }; var url = eventType == EventType.Add ? TraktUris.SyncCollectionAdd : TraktUris.SyncCollectionRemove; using (var response = await PostToTrakt(url, data, cancellationToken, traktUser).ConfigureAwait(false)) { return(_jsonSerializer.DeserializeFromStream <TraktSyncResponse>(response)); } }
private async Task SetRequestHeaders(HttpRequestOptions options, TraktUser traktUser) { options.RequestHeaders.Add("trakt-api-version", "2"); options.RequestHeaders.Add("trakt-api-key", TraktUris.Id); if (traktUser != null) { if (DateTime.Now > traktUser.AccessTokenExpiration) { traktUser.AccessToken = ""; } if (string.IsNullOrEmpty(traktUser.AccessToken) || !string.IsNullOrEmpty(traktUser.PIN)) { await RefreshUserAuth(traktUser); } if (!string.IsNullOrEmpty(traktUser.AccessToken)) { options.RequestHeaders.Add("Authorization", "Bearer " + traktUser.AccessToken); } } }
/// <summary> /// Sync watched and collected status of <see cref="Movie"/>s with trakt. /// </summary> private async Task SyncMovies( Jellyfin.Data.Entities.User user, TraktUser traktUser, ISplittableProgress <double> progress, CancellationToken cancellationToken) { /* * In order to sync watched status to trakt.tv we need to know what's been watched on Trakt already. This * will stop us from endlessly incrementing the watched values on the site. */ var traktWatchedMovies = await _traktApi.SendGetAllWatchedMoviesRequest(traktUser).ConfigureAwait(false); var traktCollectedMovies = await _traktApi.SendGetAllCollectedMoviesRequest(traktUser).ConfigureAwait(false); var libraryMovies = _libraryManager.GetItemList( new InternalItemsQuery(user) { IncludeItemTypes = new[] { BaseItemKind.Movie }, IsVirtualItem = false, OrderBy = new[]
private async Task SendEpisodeCollectionUpdates( bool collected, TraktUser traktUser, List <Episode> collectedEpisodes, ISplittableProgress <double> progress, CancellationToken cancellationToken) { _logger.Info("Episodes to add to Collection: " + collectedEpisodes.Count); if (collectedEpisodes.Count > 0) { try { var dataContracts = await _traktApi.SendLibraryUpdateAsync( collectedEpisodes, traktUser, cancellationToken, collected?EventType.Add : EventType.Remove).ConfigureAwait(false); if (dataContracts != null) { foreach (var traktSyncResponse in dataContracts) { LogTraktResponseDataContract(traktSyncResponse); } } } catch (ArgumentNullException argNullEx) { _logger.ErrorException("ArgumentNullException handled sending episodes to trakt.tv", argNullEx); } catch (Exception e) { _logger.ErrorException("Exception handled sending episodes to trakt.tv", e); } progress.Report(100); } }
private async Task SendMovieCollectionUpdates( bool collected, TraktUser traktUser, List <Movie> movies, ISplittableProgress <double> progress, CancellationToken cancellationToken) { _logger.LogInformation("Movies to " + (collected ? "add to" : "remove from") + " Collection: " + movies.Count); if (movies.Count > 0) { try { var dataContracts = await _traktApi.SendLibraryUpdateAsync( movies, traktUser, cancellationToken, collected?EventType.Add : EventType.Remove).ConfigureAwait(false); if (dataContracts != null) { foreach (var traktSyncResponse in dataContracts) { LogTraktResponseDataContract(traktSyncResponse); } } } catch (ArgumentNullException argNullEx) { _logger.LogError(argNullEx, "ArgumentNullException handled sending movies to trakt.tv"); } catch (Exception e) { _logger.LogError(e, "Exception handled sending movies to trakt.tv"); } progress.Report(100); } }
public async Task RefreshUserAuth(TraktUser traktUser) { var data = new TraktUserTokenRequest { client_id = TraktUris.Id, client_secret = TraktUris.Secret, redirect_uri = "urn:ietf:wg:oauth:2.0:oob" }; if (!string.IsNullOrWhiteSpace(traktUser.PIN)) { data.code = traktUser.PIN; data.grant_type = "authorization_code"; } else if (!string.IsNullOrWhiteSpace(traktUser.RefreshToken)) { data.code = traktUser.RefreshToken; data.grant_type = "refresh_token"; } else { _logger.Error("Tried to reauthenticate with Trakt, but neither PIN nor refreshToken was available"); } TraktUserToken userToken; using (var response = await PostToTrakt(TraktUris.Token, data, null)) { userToken = _jsonSerializer.DeserializeFromStream <TraktUserToken>(response); } if (userToken != null) { traktUser.AccessToken = userToken.access_token; traktUser.RefreshToken = userToken.refresh_token; traktUser.PIN = null; traktUser.AccessTokenExpiration = DateTime.Now.AddMonths(2); Plugin.Instance.SaveConfiguration(); } }
/// <summary> /// Delete all media from a users trakt.tv library, including watch history and then add all media that's stored /// in the MB server library to the trakt.tv library. /// Intended to be used to clean a users library. THIS IS A DESTRUCTIVE EVENT. /// </summary> /// <param name="traktUser"></param> /// <returns></returns> public async Task ResetTraktTvLibrary(TraktUser traktUser) { // Get a list of all the media in a users library var allMovies = await SendGetAllMoviesRequest(traktUser).ConfigureAwait(false); var allShows = await SendGetCollectionShowsRequest(traktUser).ConfigureAwait(false); // then delete them all if (allMovies != null && allMovies.Any()) { } if (allShows != null && allShows.Any()) { foreach (var show in allShows) { var data = new { username = traktUser.UserName, password = traktUser.PasswordHash, tvdb_id = show.TvdbId, title = show.Title, year = show.Year }; var options = new HttpRequestOptions { RequestContent = _jsonSerializer.SerializeToString(data), ResourcePool = Plugin.Instance.TraktResourcePool, CancellationToken = CancellationToken.None, Url = TraktUris.ShowUnLibrary }; await _httpClient.Post(options).ConfigureAwait(false); } } // How to manually run the 'SyncLibraryTask' so that we add back a 'clean' library? }
private async Task SetRequestHeaders(HttpRequestOptions options, TraktUser traktUser) { options.RequestHeaders.Add("trakt-api-version", "2"); options.RequestHeaders.Add("trakt-api-key", TraktUris.Devkey); if (traktUser != null) { if (string.IsNullOrEmpty(traktUser.UserToken)) { var userToken = await GetUserToken(traktUser); if (userToken != null) { traktUser.UserToken = userToken.Token; } } if (!string.IsNullOrEmpty(traktUser.UserToken)) { options.RequestHeaders.Add("trakt-user-login", traktUser.UserName); options.RequestHeaders.Add("trakt-user-token", traktUser.UserToken); } } }
internal static void FollowUser(TraktUser user) { Thread followUserThread = new Thread(delegate(object obj) { var currUser = obj as TraktUser; var response = TraktAPI.TraktAPI.NetworkFollow(CreateNetworkData(currUser)); TraktLogger.LogTraktResponse <TraktNetworkFollowResponse>(response); // notify user if follow is pending approval by user if (response.Pending) { GUIUtils.ShowNotifyDialog(Translation.Follow, string.Format(Translation.FollowPendingApproval, currUser.Username)); } }) { IsBackground = true, Name = "FollowUser" }; followUserThread.Start(user); }
private async Task <Stream> GetFromTrakt(string url, TraktUser traktUser, CancellationToken cancellationToken) { var httpClient = GetHttpClient(); if (traktUser != null) { await SetRequestHeaders(httpClient, traktUser).ConfigureAwait(false); } await _traktResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); try { var response = await RetryHttpRequest(async() => await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); return(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false)); } finally { _traktResourcePool.Release(); } }
/// <summary> /// Checks whether it's possible/allowed to sync a <see cref="BaseItem"/> for a <see cref="TraktUser"/>. /// </summary> /// <param name="item"> /// Item to check. /// </param> /// <param name="traktUser"> /// The trakt user to check for. /// </param> /// <returns> /// <see cref="bool"/> indicates if it's possible/allowed to sync this item. /// </returns> public bool CanSync(BaseItem item, TraktUser traktUser) { if (item.Path == null || item.LocationType == LocationType.Virtual) { return false; } if (traktUser.LocationsExcluded != null && traktUser.LocationsExcluded.Any(s => _fileSystem.ContainsSubPath(s, item.Path))) { return false; } if (item is Movie movie) { return !string.IsNullOrEmpty(movie.GetProviderId(MetadataProviders.Imdb)) || !string.IsNullOrEmpty(movie.GetProviderId(MetadataProviders.Tmdb)); } if (item is Episode episode && episode.Series != null && !episode.IsMissingEpisode && (episode.IndexNumber.HasValue || !string.IsNullOrEmpty(episode.GetProviderId(MetadataProviders.Tvdb)))) { var series = episode.Series; return !string.IsNullOrEmpty(series.GetProviderId(MetadataProviders.Imdb)) || !string.IsNullOrEmpty(series.GetProviderId(MetadataProviders.Tvdb)); } if (item is Series show) { return !string.IsNullOrEmpty(show.GetProviderId(MetadataProviders.Imdb)) || !string.IsNullOrEmpty(show.GetProviderId(MetadataProviders.Tvdb)) || !string.IsNullOrEmpty(show.GetProviderId(MetadataProviders.Tmdb)) || !string.IsNullOrEmpty(show.GetProviderId(MetadataProviders.TvRage)); } return false; }
private async Task SendMovieCollectionRemoves( TraktUser traktUser, List <TraktMovieCollected> movies, ISplittableProgress <double> progress, CancellationToken cancellationToken) { _logger.Info("Movies to remove from collection: " + movies.Count); if (movies.Count > 0) { try { var dataContracts = await _traktApi.SendCollectionRemovalsAsync( movies.Select(m => m.movie).ToList(), traktUser, cancellationToken).ConfigureAwait(false); if (dataContracts != null) { foreach (var traktSyncResponse in dataContracts) { LogTraktResponseDataContract(traktSyncResponse); } } } catch (ArgumentNullException argNullEx) { _logger.ErrorException("ArgumentNullException handled sending movies to trakt.tv", argNullEx); } catch (Exception e) { _logger.ErrorException("Exception handled sending movies to trakt.tv", e); } progress.Report(100); } }
private async Task SendEpisodeCollectionRemovals( TraktUser traktUser, List <Api.DataContracts.Sync.Collection.TraktShowCollected> uncollectedEpisodes, ISplittableProgress <double> progress, CancellationToken cancellationToken) { _logger.Info("Episodes to remove from Collection: " + uncollectedEpisodes.Count); if (uncollectedEpisodes.Count > 0) { try { var dataContracts = await _traktApi.SendLibraryRemovalsAsync( uncollectedEpisodes, traktUser, cancellationToken).ConfigureAwait(false); if (dataContracts != null) { foreach (var traktSyncResponse in dataContracts) { LogTraktResponseDataContract(traktSyncResponse); } } } catch (ArgumentNullException argNullEx) { _logger.ErrorException("ArgumentNullException handled sending episodes to trakt.tv", argNullEx); } catch (Exception e) { _logger.ErrorException("Exception handled sending episodes to trakt.tv", e); } progress.Report(100); } }
internal static void FollowUser(TraktUser user) { var followUserThread = new Thread(obj => { var currUser = obj as TraktUser; var response = TraktAPI.TraktAPI.NetworkFollowUser(currUser.Username); TraktLogger.LogTraktResponse <TraktNetworkApproval>(response); // notify user if follow is pending approval by user // approved date will be null if user is marked as private if (response != null && response.ApprovedAt == null) { GUIUtils.ShowNotifyDialog(Translation.Follow, string.Format(Translation.FollowPendingApproval, currUser.Username)); } }) { IsBackground = true, Name = "FollowUser" }; followUserThread.Start(user); }
/// <summary> /// Posts data to url, authenticating with <see cref="TraktUser"/>. /// </summary> /// <param name="traktUser">If null, authentication headers not added.</param> private async Task <Stream> PostToTrakt( string url, object data, CancellationToken cancellationToken, TraktUser traktUser) { var requestContent = data == null ? string.Empty : _jsonSerializer.SerializeToString(data); if (traktUser != null && traktUser.ExtraLogging) { _logger.LogDebug(requestContent); } var options = GetHttpRequestOptions(); options.Url = url; options.CancellationToken = cancellationToken; options.RequestContent = requestContent; if (traktUser != null) { await SetRequestHeaders(options, traktUser).ConfigureAwait(false); } await _traktResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); try { var retryResponse = await Retry(async() => await _httpClient.Post(options).ConfigureAwait(false)).ConfigureAwait(false); return(retryResponse.Content); } finally { _traktResourcePool.Release(); } }
/// <summary> /// Posts data to url, authenticating with <see cref="TraktUser"/>. /// </summary> /// <param name="traktUser">If null, authentication headers not added.</param> private async Task <Stream> PostToTrakt( string url, object data, TraktUser traktUser, CancellationToken cancellationToken) { if (traktUser != null && traktUser.ExtraLogging) { _logger.LogDebug("{@JsonData}", data); } var httpClient = GetHttpClient(); if (traktUser != null) { await SetRequestHeaders(httpClient, traktUser).ConfigureAwait(false); } var bytes = JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions); var content = new ByteArrayContent(bytes); content.Headers.Add(HeaderNames.ContentType, MediaTypeNames.Application.Json); await _traktResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); try { var response = await RetryHttpRequest(async() => await httpClient.PostAsync(url, content, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); return(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false)); } finally { _traktResourcePool.Release(); } }
/// <summary> /// Send a list of episodes to trakt.tv that have been marked watched or unwatched /// </summary> /// <param name="episodes">The list of episodes to send</param> /// <param name="traktUser">The trakt user profile that is being updated</param> /// <param name="seen">True if episodes are being marked seen, false otherwise</param> /// <param name="cancellationToken">The Cancellation Token</param> /// <returns></returns> public async Task <List <TraktResponseDataContract> > SendEpisodePlaystateUpdates(List <Episode> episodes, TraktUser traktUser, bool seen, CancellationToken cancellationToken) { if (episodes == null) { throw new ArgumentNullException("episodes"); } if (traktUser == null) { throw new ArgumentNullException("traktUser"); } var episodesPayload = episodes.Select(ep => new { season = ep.ParentIndexNumber, episode = ep.IndexNumber }).Cast <object>().ToList(); var traktResponses = new List <TraktResponseDataContract>(); var payloadParcel = new List <object>(); foreach (var episode in episodesPayload) { payloadParcel.Add(episode); if (payloadParcel.Count == 100) { var response = await SendEpisodePlaystateUpdatesInternalAsync(payloadParcel, episodes[0].Series, traktUser, seen, cancellationToken); if (response != null) { traktResponses.Add(response); } payloadParcel.Clear(); } } if (payloadParcel.Count > 0) { var response = await SendEpisodePlaystateUpdatesInternalAsync(payloadParcel, episodes[0].Series, traktUser, seen, cancellationToken); if (response != null) { traktResponses.Add(response); } } return(traktResponses); }
/// <summary> /// Sync watched and collected status of <see cref="Movie"/>s with trakt. /// </summary> private async Task SyncShows( User user, TraktUser traktUser, ISplittableProgress <double> progress, CancellationToken cancellationToken) { var traktWatchedShows = await _traktApi.SendGetWatchedShowsRequest(traktUser, cancellationToken).ConfigureAwait(false); var traktCollectedShows = await _traktApi.SendGetCollectedShowsRequest(traktUser, cancellationToken).ConfigureAwait(false); var episodeItems = _libraryManager.GetItemList( new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Episode).Name }, IsVirtualItem = false, OrderBy = new[] { new ValueTuple <string, SortOrder>(ItemSortBy.SeriesSortName, SortOrder.Ascending) } }) .Where(x => _traktApi.CanSync(x, traktUser)) .ToList(); var series = _libraryManager.GetItemList( new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Series).Name }, IsVirtualItem = false }) .Where(x => _traktApi.CanSync(x, traktUser)) .OfType <Series>() .ToList(); var collectedEpisodes = new List <Episode>(); var uncollectedShows = new List <Api.DataContracts.Sync.Collection.TraktShowCollected>(); var playedEpisodes = new List <Episode>(); var unplayedEpisodes = new List <Episode>(); var decisionProgress = progress.Split(4).Split(episodeItems.Count); foreach (var child in episodeItems) { cancellationToken.ThrowIfCancellationRequested(); var episode = child as Episode; var userData = _userDataManager.GetUserData(user, episode); var isPlayedTraktTv = false; var traktWatchedShow = Match.FindMatch(episode.Series, traktWatchedShows); if (traktWatchedShow?.seasons != null && traktWatchedShow.seasons.Count > 0) { isPlayedTraktTv = traktWatchedShow.seasons.Any( season => season.number == episode.GetSeasonNumber() && season.episodes != null && season.episodes.Any(te => te.number == episode.IndexNumber && te.plays > 0)); } // if the show has been played locally and is unplayed on trakt.tv then add it to the list if (userData != null && userData.Played && !isPlayedTraktTv) { if (traktUser.PostWatchedHistory) { playedEpisodes.Add(episode); } else if (!traktUser.SkipUnwatchedImportFromTrakt) { if (userData.Played) { userData.Played = false; _userDataManager.SaveUserData( user.InternalId, episode, userData, UserDataSaveReason.Import, cancellationToken); } } } else if (userData != null && !userData.Played && isPlayedTraktTv) { // If the show has not been played locally but is played on trakt.tv then add it to the unplayed list unplayedEpisodes.Add(episode); } var traktCollectedShow = Match.FindMatch(episode.Series, traktCollectedShows); if (traktCollectedShow?.seasons == null || traktCollectedShow.seasons.All(x => x.number != episode.ParentIndexNumber) || traktCollectedShow.seasons.First(x => x.number == episode.ParentIndexNumber) .episodes.All(e => e.number != episode.IndexNumber)) { collectedEpisodes.Add(episode); } decisionProgress.Report(100); } // Check if we have all the collected items, add missing to uncollectedShows foreach (var traktShowCollected in traktCollectedShows) { _logger.Debug(_jsonSerializer.SerializeToString(series)); var seriesMatch = Match.FindMatch(traktShowCollected.show, series); if (seriesMatch != null) { var seriesEpisodes = episodeItems.OfType <Episode>().Where(e => e.Series.Id == seriesMatch.Id); var uncollectedSeasons = new List <TraktShowCollected.TraktSeasonCollected>(); foreach (var traktSeasonCollected in traktShowCollected.seasons) { var uncollectedEpisodes = new List <TraktEpisodeCollected>(); foreach (var traktEpisodeCollected in traktSeasonCollected.episodes) { if (seriesEpisodes.Any(e => e.ParentIndexNumber == traktSeasonCollected.number && e.IndexNumber == traktEpisodeCollected.number)) { } else { _logger.Debug("Could not match S{0}E{1} from {2} to any Emby episode, marking for collection removal", traktSeasonCollected.number, traktEpisodeCollected.number, _jsonSerializer.SerializeToString(traktShowCollected.show)); uncollectedEpisodes.Add(new TraktEpisodeCollected() { number = traktEpisodeCollected.number }); } } if (uncollectedEpisodes.Any()) { uncollectedSeasons.Add(new TraktShowCollected.TraktSeasonCollected() { number = traktSeasonCollected.number, episodes = uncollectedEpisodes }); } } if (uncollectedSeasons.Any()) { uncollectedShows.Add(new TraktShowCollected() { ids = traktShowCollected.show.ids, title = traktShowCollected.show.title, year = traktShowCollected.show.year, seasons = uncollectedSeasons }); } } else { _logger.Debug("Could not match {0} to any Emby show, marking for collection removal", _jsonSerializer.SerializeToString(traktShowCollected.show)); uncollectedShows.Add(new TraktShowCollected() { ids = traktShowCollected.show.ids, title = traktShowCollected.show.title, year = traktShowCollected.show.year }); } } if (traktUser.SyncCollection) { await SendEpisodeCollectionAdds(traktUser, collectedEpisodes, progress.Split(4), cancellationToken) .ConfigureAwait(false); await SendEpisodeCollectionRemovals(traktUser, uncollectedShows, progress.Split(5), cancellationToken) .ConfigureAwait(false); } await SendEpisodePlaystateUpdates(true, traktUser, playedEpisodes, progress.Split(4), cancellationToken).ConfigureAwait(false); await SendEpisodePlaystateUpdates(false, traktUser, unplayedEpisodes, progress.Split(4), cancellationToken).ConfigureAwait(false); }
private async Task SyncUserLibrary(User user, TraktUser traktUser, double progPercent, double percentPerUser, IProgress <double> progress, CancellationToken cancellationToken) { var libraryRoot = user.RootFolder; // purely for progress reporting var mediaItemsCount = libraryRoot.GetRecursiveChildren(user).Count(i => _traktApi.CanSync(i, traktUser)); if (mediaItemsCount == 0) { _logger.Info("No media found for '" + user.Name + "'."); return; } _logger.Info(mediaItemsCount + " Items found for '" + user.Name + "'."); var percentPerItem = (float)percentPerUser / mediaItemsCount / 2.0; /* * In order to sync watched status to trakt.tv we need to know what's been watched on Trakt already. This * will stop us from endlessly incrementing the watched values on the site. */ var traktWatchedMovies = await _traktApi.SendGetAllWatchedMoviesRequest(traktUser).ConfigureAwait(false); var traktCollectedMovies = await _traktApi.SendGetAllCollectedMoviesRequest(traktUser).ConfigureAwait(false); var movieItems = libraryRoot.GetRecursiveChildren(user) .Where(x => x is Movie) .Where(x => _traktApi.CanSync(x, traktUser)) .OrderBy(x => x.Name) .ToList(); var movies = new List <Movie>(); var playedMovies = new List <Movie>(); var unPlayedMovies = new List <Movie>(); foreach (var child in movieItems) { cancellationToken.ThrowIfCancellationRequested(); var movie = child as Movie; var userData = _userDataManager.GetUserData(user.Id, child.GetUserDataKey()); var collectedMovies = SyncFromTraktTask.FindMatches(movie, traktCollectedMovies).ToList(); if (!collectedMovies.Any() || collectedMovies.All(collectedMovie => collectedMovie.MetadataIsDifferent(movie))) { movies.Add(movie); } var movieWatched = SyncFromTraktTask.FindMatch(movie, traktWatchedMovies); if (userData.Played) { if (movieWatched == null) { playedMovies.Add(movie); } } else { if (movieWatched != null) { unPlayedMovies.Add(movie); } } // purely for progress reporting progPercent += percentPerItem; progress.Report(progPercent); } _logger.Info("Movies to add to Collection: " + movies.Count); // send any remaining entries if (movies.Count > 0) { try { var dataContracts = await _traktApi.SendLibraryUpdateAsync(movies, traktUser, cancellationToken, EventType.Add) .ConfigureAwait(false); if (dataContracts != null) { foreach (var traktSyncResponse in dataContracts) { LogTraktResponseDataContract(traktSyncResponse); } } } catch (ArgumentNullException argNullEx) { _logger.ErrorException("ArgumentNullException handled sending movies to trakt.tv", argNullEx); } catch (Exception e) { _logger.ErrorException("Exception handled sending movies to trakt.tv", e); } // purely for progress reporting progPercent += (percentPerItem * movies.Count); progress.Report(progPercent); } _logger.Info("Movies to set watched: " + playedMovies.Count); if (playedMovies.Count > 0) { try { var dataContracts = await _traktApi.SendMoviePlaystateUpdates(playedMovies, traktUser, true, cancellationToken); if (dataContracts != null) { foreach (var traktSyncResponse in dataContracts) { LogTraktResponseDataContract(traktSyncResponse); } } } catch (Exception e) { _logger.ErrorException("Error updating movie play states", e); } // purely for progress reporting progPercent += (percentPerItem * playedMovies.Count); progress.Report(progPercent); } _logger.Info("Movies to set unwatched: " + unPlayedMovies.Count); if (unPlayedMovies.Count > 0) { try { var dataContracts = await _traktApi.SendMoviePlaystateUpdates(unPlayedMovies, traktUser, false, cancellationToken); if (dataContracts != null) { foreach (var traktSyncResponse in dataContracts) { LogTraktResponseDataContract(traktSyncResponse); } } } catch (Exception e) { _logger.ErrorException("Error updating movie play states", e); } // purely for progress reporting progPercent += (percentPerItem * unPlayedMovies.Count); progress.Report(progPercent); } var traktWatchedShows = await _traktApi.SendGetWatchedShowsRequest(traktUser).ConfigureAwait(false); var traktCollectedShows = await _traktApi.SendGetCollectedShowsRequest(traktUser).ConfigureAwait(false); var episodeItems = libraryRoot.GetRecursiveChildren(user) .Where(x => x is Episode) .Where(x => _traktApi.CanSync(x, traktUser)) .OrderBy(x => x is Episode ? (x as Episode).SeriesName : null) .ToList(); var episodes = new List <Episode>(); var playedEpisodes = new List <Episode>(); var unPlayedEpisodes = new List <Episode>(); foreach (var child in episodeItems) { cancellationToken.ThrowIfCancellationRequested(); var episode = child as Episode; var userData = _userDataManager.GetUserData(user.Id, episode.GetUserDataKey()); var isPlayedTraktTv = false; var traktWatchedShow = SyncFromTraktTask.FindMatch(episode.Series, traktWatchedShows); if (traktWatchedShow != null && traktWatchedShow.Seasons != null && traktWatchedShow.Seasons.Count > 0) { isPlayedTraktTv = traktWatchedShow.Seasons.Any( season => season.Number == episode.GetSeasonNumber() && season.Episodes != null && season.Episodes.Any(te => te.Number == episode.IndexNumber && te.Plays > 0)); } // if the show has been played locally and is unplayed on trakt.tv then add it to the list if (userData != null && userData.Played && !isPlayedTraktTv) { playedEpisodes.Add(episode); } // If the show has not been played locally but is played on trakt.tv then add it to the unplayed list else if (userData != null && !userData.Played && isPlayedTraktTv) { unPlayedEpisodes.Add(episode); } var traktCollectedShow = SyncFromTraktTask.FindMatch(episode.Series, traktCollectedShows); if (traktCollectedShow == null || traktCollectedShow.Seasons == null || traktCollectedShow.Seasons.All(x => x.Number != episode.ParentIndexNumber) || traktCollectedShow.Seasons.First(x => x.Number == episode.ParentIndexNumber) .Episodes.All(e => e.Number != episode.IndexNumber)) { episodes.Add(episode); } // purely for progress reporting progPercent += percentPerItem; progress.Report(progPercent); } _logger.Info("Episodes to add to Collection: " + episodes.Count); if (episodes.Count > 0) { try { var dataContracts = await _traktApi.SendLibraryUpdateAsync(episodes, traktUser, cancellationToken, EventType.Add) .ConfigureAwait(false); if (dataContracts != null) { foreach (var traktSyncResponse in dataContracts) { LogTraktResponseDataContract(traktSyncResponse); } } } catch (ArgumentNullException argNullEx) { _logger.ErrorException("ArgumentNullException handled sending episodes to trakt.tv", argNullEx); } catch (Exception e) { _logger.ErrorException("Exception handled sending episodes to trakt.tv", e); } // purely for progress reporting progPercent += (percentPerItem * episodes.Count); progress.Report(progPercent); } _logger.Info("Episodes to set watched: " + playedEpisodes.Count); if (playedEpisodes.Count > 0) { try { var dataContracts = await _traktApi.SendEpisodePlaystateUpdates(playedEpisodes, traktUser, true, cancellationToken); if (dataContracts != null) { foreach (var traktSyncResponse in dataContracts) { LogTraktResponseDataContract(traktSyncResponse); } } } catch (Exception e) { _logger.ErrorException("Error updating episode play states", e); } // purely for progress reporting progPercent += (percentPerItem * playedEpisodes.Count); progress.Report(progPercent); } _logger.Info("Episodes to set unwatched: " + unPlayedEpisodes.Count); if (unPlayedEpisodes.Count > 0) { try { var dataContracts = await _traktApi.SendEpisodePlaystateUpdates(unPlayedEpisodes, traktUser, false, cancellationToken); if (dataContracts != null) { foreach (var traktSyncResponse in dataContracts) { LogTraktResponseDataContract(traktSyncResponse); } } } catch (Exception e) { _logger.ErrorException("Error updating episode play states", e); } // purely for progress reporting progPercent += (percentPerItem * unPlayedEpisodes.Count); progress.Report(progPercent); } }
/// <summary> /// Sync watched and collected status of <see cref="Movie"/>s with trakt. /// </summary> private async Task SyncMovies( User user, TraktUser traktUser, ISplittableProgress <double> progress, CancellationToken cancellationToken) { /* * In order to sync watched status to trakt.tv we need to know what's been watched on Trakt already. This * will stop us from endlessly incrementing the watched values on the site. */ var traktWatchedMovies = await _traktApi.SendGetAllWatchedMoviesRequest(traktUser, cancellationToken).ConfigureAwait(false); var traktCollectedMovies = await _traktApi.SendGetAllCollectedMoviesRequest(traktUser, cancellationToken).ConfigureAwait(false); var libraryMovies = _libraryManager.GetItemList( new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Movie).Name }, IsVirtualItem = false, OrderBy = new[] { new ValueTuple <string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) } }) .Where(x => _traktApi.CanSync(x, traktUser)) .ToList(); var collectedMovies = new List <Movie>(); var uncollectedMovies = new List <TraktMovieCollected>(); var playedMovies = new List <Movie>(); var unplayedMovies = new List <Movie>(); var decisionProgress = progress.Split(4).Split(libraryMovies.Count); foreach (var child in libraryMovies) { cancellationToken.ThrowIfCancellationRequested(); var libraryMovie = child as Movie; var userData = _userDataManager.GetUserData(user, child); // if movie is not collected, or (export media info setting is enabled and every collected matching movie has different metadata), collect it var collectedMathingMovies = Match.FindMatches(libraryMovie, traktCollectedMovies).ToList(); if (!collectedMathingMovies.Any() || (traktUser.ExportMediaInfo && collectedMathingMovies.All( collectedMovie => collectedMovie.MetadataIsDifferent(libraryMovie)))) { collectedMovies.Add(libraryMovie); } var movieWatched = Match.FindMatch(libraryMovie, traktWatchedMovies); // if the movie has been played locally and is unplayed on trakt.tv then add it to the list if (userData.Played) { if (movieWatched == null) { if (traktUser.PostWatchedHistory) { playedMovies.Add(libraryMovie); } else if (!traktUser.SkipUnwatchedImportFromTrakt) { if (userData.Played) { userData.Played = false; _userDataManager.SaveUserData( user.InternalId, libraryMovie, userData, UserDataSaveReason.Import, cancellationToken); } } } } else { // If the show has not been played locally but is played on trakt.tv then add it to the unplayed list if (movieWatched != null) { unplayedMovies.Add(libraryMovie); } } decisionProgress.Report(100); } foreach (var traktCollectedMovie in traktCollectedMovies) { if (!Match.FindMatches(traktCollectedMovie, libraryMovies).Any()) { _logger.Debug("No matches for {0}, will be uncollected on Trakt", _jsonSerializer.SerializeToString(traktCollectedMovie.movie)); uncollectedMovies.Add(traktCollectedMovie); } } if (traktUser.SyncCollection) { // send movies to mark collected await SendMovieCollectionAdds(traktUser, collectedMovies, progress.Split(4), cancellationToken).ConfigureAwait(false); // send movies to mark uncollected await SendMovieCollectionRemoves(traktUser, uncollectedMovies, progress.Split(4), cancellationToken).ConfigureAwait(false); } // send movies to mark watched await SendMoviePlaystateUpdates(true, traktUser, playedMovies, progress.Split(4), cancellationToken).ConfigureAwait(false); // send movies to mark unwatched await SendMoviePlaystateUpdates(false, traktUser, unplayedMovies, progress.Split(4), cancellationToken).ConfigureAwait(false); }
/// <summary> /// /// </summary> /// <param name="events"></param> /// <param name="traktUser"></param> /// <param name="eventType"></param> /// <returns></returns> private async Task ProcessQueuedMovieEvents(IEnumerable <LibraryEvent> events, TraktUser traktUser, EventType eventType) { var movies = events.Select(lev => (Movie)lev.Item) .Where(lev => !string.IsNullOrEmpty(lev.Name) && !string.IsNullOrEmpty(lev.GetProviderId(MetadataProviders.Imdb))) .ToList(); try { await _traktApi.SendLibraryUpdateAsync(movies, traktUser, CancellationToken.None, eventType).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError("Exception handled processing queued movie events", ex); } }
/// <summary> /// /// </summary> /// <param name="events"></param> /// <param name="traktUser"></param> /// <param name="eventType"></param> /// <returns></returns> private async Task ProcessQueuedEpisodeEvents(IEnumerable <LibraryEvent> events, TraktUser traktUser, EventType eventType) { var episodes = events.Select(lev => (Episode)lev.Item) .Where(lev => lev.Series != null && (!string.IsNullOrEmpty(lev.Series.Name) && !string.IsNullOrEmpty(lev.Series.GetProviderId(MetadataProviders.Tvdb)))) .OrderBy(i => i.Series.Id) .ToList(); // Can't progress further without episodes if (!episodes.Any()) { _logger.LogInformation("episodes count is 0"); return; } var payload = new List <Episode>(); var currentSeriesId = episodes[0].Series.Id; foreach (var ep in episodes) { if (!currentSeriesId.Equals(ep.Series.Id)) { // We're starting a new series. Time to send the current one to trakt.tv await _traktApi.SendLibraryUpdateAsync(payload, traktUser, CancellationToken.None, eventType); currentSeriesId = ep.Series.Id; payload.Clear(); } payload.Add(ep); } if (payload.Any()) { try { await _traktApi.SendLibraryUpdateAsync(payload, traktUser, CancellationToken.None, eventType); } catch (Exception ex) { _logger.LogError("Exception handled processing queued episode events", ex); } } }
private async Task ProcessQueuedShowEvents(IEnumerable <LibraryEvent> events, TraktUser traktUser, EventType eventType) { var shows = events.Select(lev => (Series)lev.Item) .Where(lev => !string.IsNullOrEmpty(lev.Name) && !string.IsNullOrEmpty(lev.GetProviderId(MetadataProviders.Tvdb))) .ToList(); try { // Should probably not be awaiting this, but it's unlikely a user will be deleting more than one or two shows at a time foreach (var show in shows) { await _traktApi.SendLibraryUpdateAsync(show, traktUser, CancellationToken.None, eventType).ConfigureAwait(false); } } catch (Exception ex) { _logger.LogError("Exception handled processing queued series events", ex); } }
/// <summary> /// /// </summary> /// <param name="userDataSaveEventArgs"></param> /// <param name="traktUser"></param> public async Task ProcessUserDataSaveEventArgs(UserDataSaveEventArgs userDataSaveEventArgs, TraktUser traktUser, CancellationToken cancellationToken) { var userPackage = _userDataPackages.FirstOrDefault(e => e.TraktUser.Equals(traktUser)); if (userPackage == null) { userPackage = new UserDataPackage { TraktUser = traktUser }; _userDataPackages.Add(userPackage); } if (_timer == null) { _timer = new Timer(OnTimerCallback, null, TimeSpan.FromMilliseconds(5000), Timeout.InfiniteTimeSpan); } else { _timer.Change(TimeSpan.FromMilliseconds(5000), Timeout.InfiniteTimeSpan); } var movie = userDataSaveEventArgs.Item as Movie; if (movie != null) { if (userDataSaveEventArgs.UserData.Played) { userPackage.SeenMovies.Add(movie); if (userPackage.SeenMovies.Count >= 100) { await _traktApi.SendMoviePlaystateUpdates(userPackage.SeenMovies, userPackage.TraktUser, true, true, cancellationToken).ConfigureAwait(false); userPackage.SeenMovies = new List <Movie>(); } await MovieStatusUpdate(movie, userPackage.TraktUser, cancellationToken).ConfigureAwait(false); } else { userPackage.UnSeenMovies.Add(movie); if (userPackage.UnSeenMovies.Count >= 100) { await _traktApi.SendMoviePlaystateUpdates(userPackage.UnSeenMovies, userPackage.TraktUser, true, false, cancellationToken).ConfigureAwait(false); userPackage.UnSeenMovies = new List <Movie>(); } } return; } var episode = userDataSaveEventArgs.Item as Episode; if (episode == null) { return; } // If it's not the series we're currently storing, upload our episodes and reset the arrays if (!userPackage.CurrentSeriesId.Equals(episode.Series.Id)) { if (userPackage.SeenEpisodes.Any()) { await _traktApi.SendEpisodePlaystateUpdates(userPackage.SeenEpisodes, userPackage.TraktUser, true, true, cancellationToken).ConfigureAwait(false); userPackage.SeenEpisodes = new List <Episode>(); } if (userPackage.UnSeenEpisodes.Any()) { await _traktApi.SendEpisodePlaystateUpdates(userPackage.UnSeenEpisodes, userPackage.TraktUser, true, false, cancellationToken).ConfigureAwait(false); userPackage.UnSeenEpisodes = new List <Episode>(); } userPackage.CurrentSeriesId = episode.Series.Id; } if (userDataSaveEventArgs.UserData.Played) { userPackage.SeenEpisodes.Add(episode); await EpisodeStatusUpdate(episode, traktUser, cancellationToken).ConfigureAwait(false); } else { userPackage.UnSeenEpisodes.Add(episode); } }
/// <summary> /// /// </summary> /// <param name="userDataSaveEventArgs"></param> /// <param name="traktUser"></param> public void ProcessUserDataSaveEventArgs(UserDataSaveEventArgs userDataSaveEventArgs, TraktUser traktUser) { var userPackage = _userDataPackages.FirstOrDefault(e => e.TraktUser.Equals(traktUser)); if (userPackage == null) { userPackage = new UserDataPackage { TraktUser = traktUser }; _userDataPackages.Add(userPackage); } if (_timer == null) { _timer = new Timer(5000); _timer.Elapsed += TimerElapsed; } if (_timer.Enabled) { _timer.Stop(); _timer.Start(); } else { _timer.Start(); } var movie = userDataSaveEventArgs.Item as Movie; if (movie != null) { if (userDataSaveEventArgs.UserData.Played) { userPackage.SeenMovies.Add(movie); if (userPackage.SeenMovies.Count >= 100) { _traktApi.SendMoviePlaystateUpdates(userPackage.SeenMovies, userPackage.TraktUser, true, CancellationToken.None).ConfigureAwait(false); userPackage.SeenMovies = new List <Movie>(); } } else { userPackage.UnSeenMovies.Add(movie); if (userPackage.UnSeenMovies.Count >= 100) { _traktApi.SendMoviePlaystateUpdates(userPackage.UnSeenMovies, userPackage.TraktUser, false, CancellationToken.None).ConfigureAwait(false); userPackage.UnSeenMovies = new List <Movie>(); } } return; } var episode = userDataSaveEventArgs.Item as Episode; if (episode == null) { return; } // If it's not the series we're currently storing, upload our episodes and reset the arrays if (!userPackage.CurrentSeriesId.Equals(episode.Series.Id)) { if (userPackage.SeenEpisodes.Any()) { _traktApi.SendEpisodePlaystateUpdates(userPackage.SeenEpisodes, userPackage.TraktUser, true, CancellationToken.None).ConfigureAwait(false); userPackage.SeenEpisodes = new List <Episode>(); } if (userPackage.UnSeenEpisodes.Any()) { _traktApi.SendEpisodePlaystateUpdates(userPackage.UnSeenEpisodes, userPackage.TraktUser, false, CancellationToken.None).ConfigureAwait(false); userPackage.SeenEpisodes = new List <Episode>(); } userPackage.CurrentSeriesId = episode.Series.Id; } if (userDataSaveEventArgs.UserData.Played) { userPackage.SeenEpisodes.Add(episode); } else { userPackage.UnSeenEpisodes.Add(episode); } }
/// <summary> /// Report to trakt.tv that a movie is being watched, or has been watched. /// </summary> /// <param name="movie">The movie being watched/scrobbled</param> /// <param name="mediaStatus">MediaStatus enum dictating whether item is being watched or scrobbled</param> /// <param name="traktUser">The user that watching the current movie</param> /// <returns>A standard TraktResponse Data Contract</returns> public async Task <TraktResponseDataContract> SendMovieStatusUpdateAsync(Movie movie, MediaStatus mediaStatus, TraktUser traktUser) { var data = new Dictionary <string, string> { { "username", traktUser.UserName }, { "password", traktUser.PasswordHash }, { "imdb_id", movie.GetProviderId(MetadataProviders.Imdb) } }; if (movie.ProviderIds != null && movie.ProviderIds.ContainsKey("Tmdb")) { data.Add("tmdb_id", movie.ProviderIds["Tmdb"]); } data.Add("title", movie.Name); data.Add("year", movie.ProductionYear != null ? movie.ProductionYear.ToString() : ""); data.Add("duration", movie.RunTimeTicks != null ? ((int)((movie.RunTimeTicks / 10000000) / 60)).ToString(CultureInfo.InvariantCulture) : ""); Stream response = null; if (mediaStatus == MediaStatus.Watching) { response = await _httpClient.Post(TraktUris.MovieWatching, data, Plugin.Instance.TraktResourcePool, CancellationToken.None).ConfigureAwait(false); } else if (mediaStatus == MediaStatus.Scrobble) { response = await _httpClient.Post(TraktUris.MovieScrobble, data, Plugin.Instance.TraktResourcePool, CancellationToken.None).ConfigureAwait(false); } return(_jsonSerializer.DeserializeFromStream <TraktResponseDataContract>(response)); }
private async Task <TraktResponseDataContract> SendEpisodePlaystateUpdatesInternalAsync(List <object> episodesPayload, Series series, TraktUser traktUser, bool seen, CancellationToken cancellationToken) { var data = new { username = traktUser.UserName, password = traktUser.PasswordHash, imdb_id = series.GetProviderId(MetadataProviders.Imdb), tvdb_id = series.GetProviderId(MetadataProviders.Tvdb), title = series.Name, year = (series.ProductionYear ?? 0).ToString(CultureInfo.InvariantCulture), episodes = episodesPayload }; var options = new HttpRequestOptions { RequestContent = _jsonSerializer.SerializeToString(data), ResourcePool = Plugin.Instance.TraktResourcePool, CancellationToken = cancellationToken, Url = seen ? TraktUris.ShowEpisodeSeen : TraktUris.ShowEpisodeUnSeen }; var response = await _httpClient.Post(options).ConfigureAwait(false); return(_jsonSerializer.DeserializeFromStream <TraktResponseDataContract>(response.Content)); }