Example #1
0
        private async Task SendEpisodePlaystateUpdates(
            bool seen,
            TraktUser traktUser,
            List <Episode> playedEpisodes,
            ISplittableProgress <double> progress,
            CancellationToken cancellationToken)
        {
            _logger.Info("Episodes to set " + (seen ? string.Empty : "un") + "watched: " + playedEpisodes.Count);
            if (playedEpisodes.Count > 0)
            {
                try
                {
                    var dataContracts =
                        await _traktApi.SendEpisodePlaystateUpdates(playedEpisodes, traktUser, false, seen, cancellationToken).ConfigureAwait(false);

                    if (dataContracts != null)
                    {
                        foreach (var con in dataContracts)
                        {
                            LogTraktResponseDataContract(con);
                        }
                    }
                }
                catch (Exception e)
                {
                    _logger.ErrorException("Error updating episode play states", e);
                }

                progress.Report(100);
            }
        }
        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);
            }
        }
Example #3
0
        /// <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);
            }
        }
        private async Task SyncUserLibrary(User user,
                                           TraktUser traktUser,
                                           double progPercent,
                                           double percentPerUser,
                                           IProgress <double> progress,
                                           CancellationToken cancellationToken)
        {
            var libraryRoot = user.RootFolder;

            /*
             * 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.
             */
            List <TraktMovieDataContract> tMovies = await _traktApi.SendGetAllMoviesRequest(traktUser).ConfigureAwait(false);

            List <TraktUserLibraryShowDataContract> tShowsWatched = await _traktApi.SendGetWatchedShowsRequest(traktUser).ConfigureAwait(false);

            var movies           = new List <Movie>();
            var episodes         = new List <Episode>();
            var playedMovies     = new List <Movie>();
            var playedEpisodes   = new List <Episode>();
            var unPlayedMovies   = new List <Movie>();
            var unPlayedEpisodes = new List <Episode>();
            var currentSeriesId  = Guid.Empty;

            var mediaItems = libraryRoot.GetRecursiveChildren(user)
                             .Where(SyncFromTraktTask.CanSync)
                             .ToList();

            if (mediaItems.Count == 0)
            {
                _logger.Info("No trakt media found for '" + user.Name + "'. Have trakt locations been configured?");
                return;
            }

            // purely for progress reporting
            var percentPerItem = percentPerUser / mediaItems.Count;

            foreach (var child in mediaItems)
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (child.Path == null || child.LocationType == LocationType.Virtual)
                {
                    continue;
                }

                if (child is Movie)
                {
                    var movie = (Movie)child;
                    movies.Add(movie);

                    var userData = _userDataManager.GetUserData(user.Id, child.GetUserDataKey());

                    var traktTvMovie = SyncFromTraktTask.FindMatch(child, tMovies);

                    if (traktTvMovie != null && userData.Played && (traktTvMovie.Watched == false || traktTvMovie.Plays < userData.PlayCount))
                    {
                        playedMovies.Add(movie);
                    }
                    else if (traktTvMovie != null && traktTvMovie.Watched && !userData.Played)
                    {
                        unPlayedMovies.Add(movie);
                    }

                    // publish if the list hits a certain size
                    if (movies.Count >= 120)
                    {
                        // Add movies to library
                        try
                        {
                            var dataContract = await _traktApi.SendLibraryUpdateAsync(movies, traktUser, cancellationToken, EventType.Add).ConfigureAwait(false);

                            if (dataContract != null)
                            {
                                LogTraktResponseDataContract(dataContract);
                            }
                        }
                        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);
                        }
                        movies.Clear();

                        // Mark movies seen
                        if (playedMovies.Count > 0)
                        {
                            try
                            {
                                var dataCotract = await _traktApi.SendMoviePlaystateUpdates(playedMovies, traktUser, true, cancellationToken);

                                if (dataCotract != null)
                                {
                                    LogTraktResponseDataContract(dataCotract);
                                }
                            }
                            catch (Exception e)
                            {
                                _logger.ErrorException("Error updating played state", e);
                            }

                            playedMovies.Clear();
                        }

                        // Mark movies unseen
                        if (unPlayedMovies.Count > 0)
                        {
                            try
                            {
                                var dataContract = await _traktApi.SendMoviePlaystateUpdates(unPlayedMovies, traktUser, false, cancellationToken);

                                if (dataContract != null)
                                {
                                    LogTraktResponseDataContract(dataContract);
                                }
                            }
                            catch (Exception e)
                            {
                                _logger.ErrorException("Error updating played state", e);
                            }

                            unPlayedMovies.Clear();
                        }
                    }
                }
                else if (child is Episode)
                {
                    var ep     = child as Episode;
                    var series = ep.Series;

                    var userData = _userDataManager.GetUserData(user.Id, ep.GetUserDataKey());

                    var isPlayedTraktTv = false;

                    var traktTvShow = SyncFromTraktTask.FindMatch(series, tShowsWatched);

                    if (traktTvShow != null && traktTvShow.Seasons != null && traktTvShow.Seasons.Count > 0)
                    {
                        foreach (var episode in from season in traktTvShow.Seasons where ep.Season != null && season.Season.Equals(ep.Season.IndexNumber) && season.Episodes != null && season.Episodes.Count > 0 from episode in season.Episodes where episode.Equals(ep.IndexNumber) select episode)
                        {
                            isPlayedTraktTv = true;
                        }
                    }

                    if (series != null && currentSeriesId != series.Id && episodes.Count > 0)
                    {
                        // We're starting a new show. Finish up with the old one

                        // Add episodes to trakt.tv library
                        try
                        {
                            var dataContract = await _traktApi.SendLibraryUpdateAsync(episodes, traktUser, cancellationToken, EventType.Add).ConfigureAwait(false);

                            if (dataContract != null)
                            {
                                LogTraktResponseDataContract(dataContract);
                            }
                        }
                        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);
                        }

                        // Update played state of these episodes
                        if (playedEpisodes.Count > 0)
                        {
                            try
                            {
                                var dataContracts = await _traktApi.SendEpisodePlaystateUpdates(playedEpisodes, traktUser, true, cancellationToken);

                                if (dataContracts != null)
                                {
                                    foreach (var dataContract in dataContracts)
                                    {
                                        LogTraktResponseDataContract(dataContract);
                                    }
                                }
                            }
                            catch (Exception e)
                            {
                                _logger.ErrorException("Exception handled sending played episodes to trakt.tv", e);
                            }
                        }

                        if (unPlayedEpisodes.Count > 0)
                        {
                            try
                            {
                                var dataContracts = await _traktApi.SendEpisodePlaystateUpdates(unPlayedEpisodes, traktUser, false, cancellationToken);

                                if (dataContracts != null)
                                {
                                    foreach (var dataContract in dataContracts)
                                    {
                                        LogTraktResponseDataContract(dataContract);
                                    }
                                }
                            }
                            catch (Exception e)
                            {
                                _logger.ErrorException("Exception handled sending played episodes to trakt.tv", e);
                            }
                        }

                        episodes.Clear();
                        playedEpisodes.Clear();
                        unPlayedEpisodes.Clear();
                    }

                    if (ep.Series != null)
                    {
                        currentSeriesId = ep.Series.Id;
                        episodes.Add(ep);
                    }

                    // 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(ep);
                    }
                    // 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(ep);
                    }
                }

                // purely for progress reporting
                progPercent += percentPerItem;
                progress.Report(progPercent);
            }

            // send any remaining entries
            if (movies.Count > 0)
            {
                try
                {
                    var dataContract = await _traktApi.SendLibraryUpdateAsync(movies, traktUser, cancellationToken, EventType.Add).ConfigureAwait(false);

                    if (dataContract != null)
                    {
                        LogTraktResponseDataContract(dataContract);
                    }
                }
                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);
                }
            }

            if (episodes.Count > 0)
            {
                try
                {
                    var dataContract = await _traktApi.SendLibraryUpdateAsync(episodes, traktUser, cancellationToken, EventType.Add).ConfigureAwait(false);

                    if (dataContract != null)
                    {
                        LogTraktResponseDataContract(dataContract);
                    }
                }
                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);
                }
            }

            if (playedMovies.Count > 0)
            {
                try
                {
                    var dataContract = await _traktApi.SendMoviePlaystateUpdates(playedMovies, traktUser, true, cancellationToken);

                    if (dataContract != null)
                    {
                        LogTraktResponseDataContract(dataContract);
                    }
                }
                catch (Exception e)
                {
                    _logger.ErrorException("Error updating movie play states", e);
                }
            }

            if (playedEpisodes.Count > 0)
            {
                try
                {
                    var dataContracts = await _traktApi.SendEpisodePlaystateUpdates(playedEpisodes, traktUser, true, cancellationToken);

                    if (dataContracts != null)
                    {
                        foreach (var dataContract in dataContracts)
                        {
                            LogTraktResponseDataContract(dataContract);
                        }
                    }
                }
                catch (Exception e)
                {
                    _logger.ErrorException("Error updating episode play states", e);
                }
            }

            if (unPlayedEpisodes.Count > 0)
            {
                try
                {
                    var dataContracts = await _traktApi.SendEpisodePlaystateUpdates(unPlayedEpisodes, traktUser, false, cancellationToken);

                    if (dataContracts != null)
                    {
                        foreach (var dataContract in dataContracts)
                        {
                            LogTraktResponseDataContract(dataContract);
                        }
                    }
                }
                catch (Exception e)
                {
                    _logger.ErrorException("Error updating episode play states", e);
                }
            }
        }
        public async Task Execute(CancellationToken cancellationToken, IProgress <double> progress)
        {
            var users = _userManager.Users.Where(u =>
            {
                var traktUser = UserHelper.GetTraktUser(u);

                return(traktUser != null && traktUser.TraktLocations != null && traktUser.TraktLocations.Length > 0);
            }).ToList();

            // No point going further if we don't have users.
            if (users.Count == 0)
            {
                _logger.Info("No Users returned");
                return;
            }

            // purely for progress reporting
            var progPercent    = 0.0;
            var percentPerUser = 100 / users.Count;

            foreach (var user in users)
            {
                var libraryRoot = user.RootFolder;
                var traktUser   = UserHelper.GetTraktUser(user);

                // I'll leave this in here for now, but in reality this continue should never be reached.
                if (traktUser == null || String.IsNullOrEmpty(traktUser.LinkedMbUserId))
                {
                    _logger.Error("traktUser is either null or has no linked MB account");
                    continue;
                }

                /*
                 * 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.
                 */
                IEnumerable <TraktMovieDataContract> tMovies = await _traktApi.SendGetAllMoviesRequest(traktUser).ConfigureAwait(false);

                IEnumerable <TraktUserLibraryShowDataContract> tShowsWatched = await _traktApi.SendGetWatchedShowsRequest(traktUser).ConfigureAwait(false);

                var movies           = new List <Movie>();
                var episodes         = new List <Episode>();
                var playedMovies     = new List <Movie>();
                var playedEpisodes   = new List <Episode>();
                var unPlayedMovies   = new List <Movie>();
                var unPlayedEpisodes = new List <Episode>();
                var currentSeriesId  = Guid.Empty;

                var mediaItems = libraryRoot.GetRecursiveChildren(user)
                                 .Where(i => i.Name != null &&
                                        (i is Episode && ((Episode)i).Series != null && ((Episode)i).Series.ProviderIds.ContainsKey("Tvdb")) ||
                                        (i is Movie && i.ProviderIds.ContainsKey("Imdb")))
                                 .OrderBy(i =>
                {
                    var episode = i as Episode;

                    return(episode != null ? episode.Series.Id : i.Id);
                })
                                 .ToList();

                if (mediaItems.Count == 0)
                {
                    _logger.Info("No trakt media found for '" + user.Name + "'. Have trakt locations been configured?");
                    continue;
                }

                // purely for progress reporting
                var percentPerItem = percentPerUser / (double)mediaItems.Count;

                foreach (var child in mediaItems)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    if (child.Path == null || child.LocationType == LocationType.Virtual)
                    {
                        continue;
                    }

                    foreach (var s in traktUser.TraktLocations.Where(s => _fileSystem.ContainsSubPath(s, child.Path)))
                    {
                        if (child is Movie)
                        {
                            var movie = child as Movie;
                            movies.Add(movie);

                            var userData = _userDataManager.GetUserData(user.Id, movie.GetUserDataKey());

                            if (movie.ProviderIds.ContainsKey("Tmdb") && userData != null)
                            {
                                var traktTvMovie =
                                    tMovies.FirstOrDefault(
                                        tMovie => tMovie.TmdbId.Equals(movie.GetProviderId(MetadataProviders.Tmdb)));

                                if (traktTvMovie != null && userData.Played && (traktTvMovie.Watched == false || traktTvMovie.Plays < userData.PlayCount))
                                {
                                    playedMovies.Add(movie);
                                }
                                else if (traktTvMovie != null && traktTvMovie.Watched && !userData.Played)
                                {
                                    unPlayedMovies.Add(movie);
                                }
                            }

                            // publish if the list hits a certain size
                            if (movies.Count >= 120)
                            {
                                // Add movies to library
                                try
                                {
                                    var dataContract = await _traktApi.SendLibraryUpdateAsync(movies, traktUser, cancellationToken, EventType.Add).ConfigureAwait(false);

                                    if (dataContract != null)
                                    {
                                        LogTraktResponseDataContract(dataContract);
                                    }
                                }
                                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);
                                }
                                movies.Clear();

                                // Mark movies seen
                                if (playedMovies.Count > 0)
                                {
                                    try
                                    {
                                        var dataCotract = await _traktApi.SendMoviePlaystateUpdates(playedMovies, traktUser, true, cancellationToken);

                                        if (dataCotract != null)
                                        {
                                            LogTraktResponseDataContract(dataCotract);
                                        }
                                    }
                                    catch (Exception e)
                                    {
                                        _logger.ErrorException("Error updating played state", e);
                                    }

                                    playedMovies.Clear();
                                }

                                // Mark movies unseen
                                if (unPlayedMovies.Count > 0)
                                {
                                    try
                                    {
                                        var dataContract = await _traktApi.SendMoviePlaystateUpdates(unPlayedMovies, traktUser, false, cancellationToken);

                                        if (dataContract != null)
                                        {
                                            LogTraktResponseDataContract(dataContract);
                                        }
                                    }
                                    catch (Exception e)
                                    {
                                        _logger.ErrorException("Error updating played state", e);
                                    }

                                    unPlayedMovies.Clear();
                                }
                            }
                        }
                        else if (child is Episode)
                        {
                            var ep = child as Episode;

                            var userData = _userDataManager.GetUserData(user.Id, ep.GetUserDataKey());

                            var isPlayedTraktTv = false;

                            if (ep.Series != null && ep.Series.ProviderIds.ContainsKey("Tvdb"))
                            {
                                var traktTvShow =
                                    tShowsWatched.FirstOrDefault(
                                        tShow => tShow.TvdbId.Equals(ep.Series.GetProviderId(MetadataProviders.Tvdb)));

                                if (traktTvShow != null && traktTvShow.Seasons != null && traktTvShow.Seasons.Count > 0)
                                {
                                    foreach (var episode in from season in traktTvShow.Seasons where ep.Season != null && season.Season.Equals(ep.Season.IndexNumber) && season.Episodes != null && season.Episodes.Count > 0 from episode in season.Episodes where episode.Equals(ep.IndexNumber) select episode)
                                    {
                                        isPlayedTraktTv = true;
                                    }
                                }
                            }

                            if (ep.Series != null && currentSeriesId != ep.Series.Id && episodes.Count > 0)
                            {
                                // We're starting a new show. Finish up with the old one

                                // Add episodes to trakt.tv library
                                try
                                {
                                    var dataContract = await _traktApi.SendLibraryUpdateAsync(episodes, traktUser, cancellationToken, EventType.Add).ConfigureAwait(false);

                                    if (dataContract != null)
                                    {
                                        LogTraktResponseDataContract(dataContract);
                                    }
                                }
                                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);
                                }

                                // Update played state of these episodes
                                if (playedEpisodes.Count > 0)
                                {
                                    try
                                    {
                                        var dataContracts = await _traktApi.SendEpisodePlaystateUpdates(playedEpisodes, traktUser, true, cancellationToken);

                                        if (dataContracts != null)
                                        {
                                            foreach (var dataContract in dataContracts)
                                            {
                                                LogTraktResponseDataContract(dataContract);
                                            }
                                        }
                                    }
                                    catch (Exception e)
                                    {
                                        _logger.ErrorException("Exception handled sending played episodes to trakt.tv", e);
                                    }
                                }

                                if (unPlayedEpisodes.Count > 0)
                                {
                                    try
                                    {
                                        var dataContracts = await _traktApi.SendEpisodePlaystateUpdates(unPlayedEpisodes, traktUser, false, cancellationToken);

                                        if (dataContracts != null)
                                        {
                                            foreach (var dataContract in dataContracts)
                                            {
                                                LogTraktResponseDataContract(dataContract);
                                            }
                                        }
                                    }
                                    catch (Exception e)
                                    {
                                        _logger.ErrorException("Exception handled sending played episodes to trakt.tv", e);
                                    }
                                }

                                episodes.Clear();
                                playedEpisodes.Clear();
                                unPlayedEpisodes.Clear();
                            }

                            if (ep.Series != null)
                            {
                                currentSeriesId = ep.Series.Id;
                                episodes.Add(ep);
                            }

                            // 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(ep);
                            }
                            // 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(ep);
                            }
                        }
                    }

                    // purely for progress reporting
                    progPercent += percentPerItem;
                    progress.Report(progPercent);
                }

                // send any remaining entries
                if (movies.Count > 0)
                {
                    try
                    {
                        var dataContract = await _traktApi.SendLibraryUpdateAsync(movies, traktUser, cancellationToken, EventType.Add).ConfigureAwait(false);

                        if (dataContract != null)
                        {
                            LogTraktResponseDataContract(dataContract);
                        }
                    }
                    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);
                    }
                }

                if (episodes.Count > 0)
                {
                    try
                    {
                        var dataContract = await _traktApi.SendLibraryUpdateAsync(episodes, traktUser, cancellationToken, EventType.Add).ConfigureAwait(false);

                        if (dataContract != null)
                        {
                            LogTraktResponseDataContract(dataContract);
                        }
                    }
                    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);
                    }
                }

                if (playedMovies.Count > 0)
                {
                    try
                    {
                        var dataContract = await _traktApi.SendMoviePlaystateUpdates(playedMovies, traktUser, true, cancellationToken);

                        if (dataContract != null)
                        {
                            LogTraktResponseDataContract(dataContract);
                        }
                    }
                    catch (Exception e)
                    {
                        _logger.ErrorException("Error updating movie play states", e);
                    }
                }

                if (playedEpisodes.Count > 0)
                {
                    try
                    {
                        var dataContracts = await _traktApi.SendEpisodePlaystateUpdates(playedEpisodes, traktUser, true, cancellationToken);

                        if (dataContracts != null)
                        {
                            foreach (var dataContract in dataContracts)
                            {
                                LogTraktResponseDataContract(dataContract);
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        _logger.ErrorException("Error updating episode play states", e);
                    }
                }

                if (unPlayedEpisodes.Count > 0)
                {
                    try
                    {
                        var dataContracts = await _traktApi.SendEpisodePlaystateUpdates(unPlayedEpisodes, traktUser, false, cancellationToken);

                        if (dataContracts != null)
                        {
                            foreach (var dataContract in dataContracts)
                            {
                                LogTraktResponseDataContract(dataContract);
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        _logger.ErrorException("Error updating episode play states", e);
                    }
                }
            }
        }