public void SyncLibrary() { TraktLogger.Info("My Videos Starting Sync"); if (TraktSettings.SyncLibrary) { // get all movies ArrayList myvideos = new ArrayList(); VideoDatabase.GetMovies(ref myvideos); List <IMDBMovie> MovieList = (from IMDBMovie movie in myvideos select movie).ToList(); #region Remove Blocked Movies MovieList.RemoveAll(m => TraktSettings.BlockedFolders.Any(f => m.Path.ToLowerInvariant().Contains(f.ToLowerInvariant()))); List <int> blockedMovieIds = new List <int>(); foreach (string file in TraktSettings.BlockedFilenames) { int pathId = 0; int movieId = 0; // get a list of ids for blocked filenames // filename seems to always be empty for an IMDBMovie object! if (VideoDatabase.GetFile(file, out pathId, out movieId, false) > 0) { blockedMovieIds.Add(movieId); } } MovieList.RemoveAll(m => blockedMovieIds.Contains(m.ID)); #endregion #region Skipped Movies Check // Remove Skipped Movies from previous Sync if (TraktSettings.SkippedMovies != null) { // allow movies to re-sync again after 7-days in the case user has addressed issue ie. edited movie or added to themoviedb.org if (TraktSettings.SkippedMovies.LastSkippedSync.FromEpoch() > DateTime.UtcNow.Subtract(new TimeSpan(7, 0, 0, 0))) { if (TraktSettings.SkippedMovies.Movies != null && TraktSettings.SkippedMovies.Movies.Count > 0) { TraktLogger.Info("Skipping {0} movies due to invalid data or movies don't exist on http://themoviedb.org. Next check will be {1}.", TraktSettings.SkippedMovies.Movies.Count, TraktSettings.SkippedMovies.LastSkippedSync.FromEpoch().Add(new TimeSpan(7, 0, 0, 0))); foreach (var movie in TraktSettings.SkippedMovies.Movies) { TraktLogger.Info("Skipping movie, Title: {0}, Year: {1}, IMDb: {2}", movie.Title, movie.Year, movie.IMDBID); MovieList.RemoveAll(m => (m.Title == movie.Title) && (m.Year.ToString() == movie.Year) && (m.IMDBNumber == movie.IMDBID)); } } } else { if (TraktSettings.SkippedMovies.Movies != null) { TraktSettings.SkippedMovies.Movies.Clear(); } TraktSettings.SkippedMovies.LastSkippedSync = DateTime.UtcNow.ToEpoch(); } } #endregion #region Already Exists Movie Check // Remove Already-Exists Movies, these are typically movies that are using aka names and no IMDb/TMDb set // When we compare our local collection with trakt collection we have english only titles, so if no imdb/tmdb exists // we need to fallback to title matching. When we sync aka names are sometimes accepted if defined on themoviedb.org so we need to // do this to revent syncing these movies every sync interval. if (TraktSettings.AlreadyExistMovies != null && TraktSettings.AlreadyExistMovies.Movies != null && TraktSettings.AlreadyExistMovies.Movies.Count > 0) { TraktLogger.Debug("Skipping {0} movies as they already exist in trakt library but failed local match previously.", TraktSettings.AlreadyExistMovies.Movies.Count.ToString()); var movies = new List <TraktMovieSync.Movie>(TraktSettings.AlreadyExistMovies.Movies); foreach (var movie in movies) { Predicate <IMDBMovie> criteria = m => (m.Title == movie.Title) && (m.Year.ToString() == movie.Year) && (m.IMDBNumber == movie.IMDBID); if (MovieList.Exists(criteria)) { TraktLogger.Debug("Skipping movie, Title: {0}, Year: {1}, IMDb: {2}", movie.Title, movie.Year, movie.IMDBID); MovieList.RemoveAll(criteria); } else { // remove as we have now removed from our local collection or updated movie signature if (TraktSettings.MoviePluginCount == 1) { TraktLogger.Debug("Removing 'AlreadyExists' movie, Title: {0}, Year: {1}, IMDb: {2}", movie.Title, movie.Year, movie.IMDBID); TraktSettings.AlreadyExistMovies.Movies.Remove(movie); } } } } #endregion TraktLogger.Info("{0} movies available to sync in My Videos database", MovieList.Count.ToString()); // get the movies that we have watched List <IMDBMovie> SeenList = MovieList.Where(m => m.Watched > 0).ToList(); TraktLogger.Info("{0} watched movies available to sync in My Videos database", SeenList.Count.ToString()); // get the movies that we have yet to watch TraktLogger.Info("Getting user {0}'s movies from trakt", TraktSettings.Username); IEnumerable <TraktLibraryMovies> traktMoviesAll = TraktAPI.TraktAPI.GetAllMoviesForUser(TraktSettings.Username); if (traktMoviesAll == null) { TraktLogger.Error("Error getting movies from trakt server, cancelling sync."); return; } TraktLogger.Info("{0} movies in trakt library", traktMoviesAll.Count().ToString()); #region Movies to Sync to Collection List <IMDBMovie> moviesToSync = new List <IMDBMovie>(MovieList); List <TraktLibraryMovies> NoLongerInOurCollection = new List <TraktLibraryMovies>(); //Filter out a list of movies we have already sync'd in our collection foreach (TraktLibraryMovies tlm in traktMoviesAll) { bool notInLocalCollection = true; // if it is in both libraries foreach (IMDBMovie libraryMovie in MovieList.Where(m => MovieMatch(m, tlm))) { // If the users IMDb Id is empty/invalid and we have matched one then set it if (BasicHandler.IsValidImdb(tlm.IMDBID) && !BasicHandler.IsValidImdb(libraryMovie.IMDBNumber)) { TraktLogger.Info("Movie '{0}' inserted IMDb Id '{1}'", libraryMovie.Title, tlm.IMDBID); libraryMovie.IMDBNumber = tlm.IMDBID; IMDBMovie details = libraryMovie; VideoDatabase.SetMovieInfoById(libraryMovie.ID, ref details); } // if it is watched in Trakt but not My Videos update // skip if movie is watched but user wishes to have synced as unseen locally if (tlm.Plays > 0 && !tlm.UnSeen && libraryMovie.Watched == 0) { TraktLogger.Info("Movie '{0}' is watched on Trakt updating Database", libraryMovie.Title); libraryMovie.Watched = 1; if (libraryMovie.DateWatched == "0001-01-01 00:00:00") { libraryMovie.DateWatched = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } IMDBMovie details = libraryMovie; VideoDatabase.SetMovieInfoById(libraryMovie.ID, ref details); if (libraryMovie.WatchedCount == 0) { VideoDatabase.SetMovieWatchedCount(libraryMovie.ID, tlm.Plays); VideoDatabase.SetMovieWatchedStatus(libraryMovie.ID, true, 0); } } // mark movies as unseen if watched locally if (tlm.UnSeen && libraryMovie.Watched > 0) { TraktLogger.Info("Movie '{0}' is unseen on Trakt, updating database", libraryMovie.Title); libraryMovie.Watched = 0; IMDBMovie details = libraryMovie; VideoDatabase.SetMovieInfoById(libraryMovie.ID, ref details); VideoDatabase.SetMovieWatchedStatus(libraryMovie.ID, false, 0); } notInLocalCollection = false; //filter out if its already in collection if (tlm.InCollection) { moviesToSync.RemoveAll(m => MovieMatch(m, tlm)); } break; } if (notInLocalCollection && tlm.InCollection) { NoLongerInOurCollection.Add(tlm); } } #endregion #region Movies to Sync to Seen Collection // filter out a list of movies already marked as watched on trakt // also filter out movie marked as unseen so we dont reset the unseen cache online List <IMDBMovie> watchedMoviesToSync = new List <IMDBMovie>(SeenList); foreach (TraktLibraryMovies tlm in traktMoviesAll.Where(t => t.Plays > 0 || t.UnSeen)) { foreach (IMDBMovie watchedMovie in SeenList.Where(m => MovieMatch(m, tlm))) { //filter out watchedMoviesToSync.Remove(watchedMovie); } } #endregion #region Sync Collection to trakt //Send Library/Collection TraktLogger.Info("{0} movies need to be added to Library", moviesToSync.Count.ToString()); foreach (IMDBMovie m in moviesToSync) { TraktLogger.Info("Sending movie to trakt library, Title: {0}, Year: {1}, IMDb: {2}", m.Title, m.Year.ToString(), m.IMDBNumber); } if (moviesToSync.Count > 0) { TraktSyncResponse response = TraktAPI.TraktAPI.SyncMovieLibrary(CreateSyncData(moviesToSync), TraktSyncModes.library); BasicHandler.InsertSkippedMovies(response); BasicHandler.InsertAlreadyExistMovies(response); TraktLogger.LogTraktResponse(response); } #endregion #region Sync Seen to trakt //Send Seen TraktLogger.Info("{0} movies need to be added to SeenList", watchedMoviesToSync.Count.ToString()); foreach (IMDBMovie m in watchedMoviesToSync) { TraktLogger.Info("Sending movie to trakt as seen, Title: {0}, Year: {1}, IMDb: {2}", m.Title, m.Year.ToString(), m.IMDBNumber); } if (watchedMoviesToSync.Count > 0) { TraktSyncResponse response = TraktAPI.TraktAPI.SyncMovieLibrary(CreateSyncData(watchedMoviesToSync), TraktSyncModes.seen); BasicHandler.InsertSkippedMovies(response); BasicHandler.InsertAlreadyExistMovies(response); TraktLogger.LogTraktResponse(response); } #endregion #region Clean Library //Dont clean library if more than one movie plugin installed if (TraktSettings.KeepTraktLibraryClean && TraktSettings.MoviePluginCount == 1) { //Remove movies we no longer have in our local database from Trakt foreach (var m in NoLongerInOurCollection) { TraktLogger.Info("Removing from Trakt Collection {0}", m.Title); } TraktLogger.Info("{0} movies need to be removed from Trakt Collection", NoLongerInOurCollection.Count.ToString()); if (NoLongerInOurCollection.Count > 0) { if (TraktSettings.AlreadyExistMovies != null && TraktSettings.AlreadyExistMovies.Movies != null && TraktSettings.AlreadyExistMovies.Movies.Count > 0) { TraktLogger.Warning("DISABLING CLEAN LIBRARY!!!, there are trakt library movies that can't be determined to be local in collection."); TraktLogger.Warning("To fix this, check the 'already exist' entries in log, then check movies in local collection against this list and ensure IMDb id is set then run sync again."); } else { //Then remove from library TraktSyncResponse response = TraktAPI.TraktAPI.SyncMovieLibrary(BasicHandler.CreateMovieSyncData(NoLongerInOurCollection), TraktSyncModes.unlibrary); TraktLogger.LogTraktResponse(response); } } } #endregion } TraktLogger.Info("My Videos Sync Completed"); }
public void SyncLibrary() { TraktLogger.Info("My Videos Starting Library Sync"); #region Get online data from cache #region Get unwatched / watched movies from trakt.tv IEnumerable <TraktMovieWatched> traktWatchedMovies = null; var traktUnWatchedMovies = TraktCache.GetUnWatchedMoviesFromTrakt(); if (traktUnWatchedMovies == null) { TraktLogger.Error("Error getting unwatched movies from trakt server, unwatched and watched sync will be skipped"); } else { TraktLogger.Info("There are {0} unwatched movies since the last sync with trakt.tv", traktUnWatchedMovies.Count()); traktWatchedMovies = TraktCache.GetWatchedMoviesFromTrakt(); if (traktWatchedMovies == null) { TraktLogger.Error("Error getting watched movies from trakt server, watched sync will be skipped"); } else { TraktLogger.Info("There are {0} watched movies in trakt.tv library", traktWatchedMovies.Count()); } } #endregion #region Get collected movies from trakt.tv var traktCollectedMovies = TraktCache.GetCollectedMoviesFromTrakt(); if (traktCollectedMovies == null) { TraktLogger.Error("Error getting collected movies from trakt server"); } else { TraktLogger.Info("There are {0} collected movies in trakt.tv library", traktCollectedMovies.Count()); } #endregion #endregion // optionally do library sync if (TraktSettings.SyncLibrary) { var collectedMovies = GetMovies(); #region Remove Blocked Movies collectedMovies.RemoveAll(m => TraktSettings.BlockedFolders.Any(f => m.Path.ToLowerInvariant().Contains(f.ToLowerInvariant()))); List <int> blockedMovieIds = new List <int>(); foreach (string file in TraktSettings.BlockedFilenames) { int pathId = 0; int movieId = 0; // get a list of ids for blocked filenames // filename seems to always be empty for an IMDBMovie object! if (VideoDatabase.GetFile(file, out pathId, out movieId, false) > 0) { blockedMovieIds.Add(movieId); } } collectedMovies.RemoveAll(m => blockedMovieIds.Contains(m.ID)); #endregion #region Remove Movies with No IDs // Remove any movies that don't have any valid online ID's e.g. IMDb ID or TMDb ID. if (TraktSettings.SkipMoviesWithNoIdsOnSync) { TraktLogger.Info("Removing movies that contain no valid online ID from sync movie list"); collectedMovies.RemoveAll(m => !BasicHandler.IsValidImdb(m.IMDBNumber)); } #endregion #region Skipped Movies Check // Remove Skipped Movies from previous Sync //TODO //if (TraktSettings.SkippedMovies != null) //{ // // allow movies to re-sync again after 7-days in the case user has addressed issue ie. edited movie or added to themoviedb.org // if (TraktSettings.SkippedMovies.LastSkippedSync.FromEpoch() > DateTime.UtcNow.Subtract(new TimeSpan(7, 0, 0, 0))) // { // if (TraktSettings.SkippedMovies.Movies != null && TraktSettings.SkippedMovies.Movies.Count > 0) // { // TraktLogger.Info("Skipping {0} movies due to invalid data or movies don't exist on http://themoviedb.org. Next check will be {1}.", TraktSettings.SkippedMovies.Movies.Count, TraktSettings.SkippedMovies.LastSkippedSync.FromEpoch().Add(new TimeSpan(7, 0, 0, 0))); // foreach (var movie in TraktSettings.SkippedMovies.Movies) // { // TraktLogger.Info("Skipping movie, Title: {0}, Year: {1}, IMDb: {2}", movie.Title, movie.Year, movie.IMDBID); // MovieList.RemoveAll(m => (m.Title == movie.Title) && (m.Year.ToString() == movie.Year) && (m.IMDBNumber == movie.IMDBID)); // } // } // } // else // { // if (TraktSettings.SkippedMovies.Movies != null) TraktSettings.SkippedMovies.Movies.Clear(); // TraktSettings.SkippedMovies.LastSkippedSync = DateTime.UtcNow.ToEpoch(); // } //} #endregion #region Already Exists Movie Check // Remove Already-Exists Movies, these are typically movies that are using aka names and no IMDb/TMDb set // When we compare our local collection with trakt collection we have english only titles, so if no imdb/tmdb exists // we need to fallback to title matching. When we sync aka names are sometimes accepted if defined on themoviedb.org so we need to // do this to revent syncing these movies every sync interval. //TODO //if (TraktSettings.AlreadyExistMovies != null && TraktSettings.AlreadyExistMovies.Movies != null && TraktSettings.AlreadyExistMovies.Movies.Count > 0) //{ // TraktLogger.Debug("Skipping {0} movies as they already exist in trakt library but failed local match previously.", TraktSettings.AlreadyExistMovies.Movies.Count.ToString()); // var movies = new List<TraktMovieSync.Movie>(TraktSettings.AlreadyExistMovies.Movies); // foreach (var movie in movies) // { // Predicate<IMDBMovie> criteria = m => (m.Title == movie.Title) && (m.Year.ToString() == movie.Year) && (m.IMDBNumber == movie.IMDBID); // if (MovieList.Exists(criteria)) // { // TraktLogger.Debug("Skipping movie, Title: {0}, Year: {1}, IMDb: {2}", movie.Title, movie.Year, movie.IMDBID); // MovieList.RemoveAll(criteria); // } // else // { // // remove as we have now removed from our local collection or updated movie signature // if (TraktSettings.MoviePluginCount == 1) // { // TraktLogger.Debug("Removing 'AlreadyExists' movie, Title: {0}, Year: {1}, IMDb: {2}", movie.Title, movie.Year, movie.IMDBID); // TraktSettings.AlreadyExistMovies.Movies.Remove(movie); // } // } // } //} #endregion TraktLogger.Info("Found {0} movies available to sync in My Videos database", collectedMovies.Count); // get the movies that we have watched var watchedMovies = collectedMovies.Where(m => m.Watched > 0).ToList(); TraktLogger.Info("Found {0} watched movies available to sync in My Videos database", watchedMovies.Count); #region Mark movies as unwatched in local database if (traktUnWatchedMovies != null && traktUnWatchedMovies.Count() > 0) { foreach (var movie in traktUnWatchedMovies) { var localMovie = watchedMovies.FirstOrDefault(m => MovieMatch(m, movie)); if (localMovie == null) { continue; } TraktLogger.Info("Marking movie as unwatched in local database, movie is not watched on trakt.tv. Title = '{0}', Year = '{1}', IMDb ID = '{2}', TMDb ID = '{3}'", movie.Title, movie.Year.HasValue ? movie.Year.ToString() : "<empty>", movie.Ids.Imdb ?? "<empty>", movie.Ids.Tmdb.HasValue ? movie.Ids.Tmdb.ToString() : "<empty>"); localMovie.Watched = 0; IMDBMovie details = localMovie; VideoDatabase.SetMovieInfoById(localMovie.ID, ref details); VideoDatabase.SetMovieWatchedStatus(localMovie.ID, false, 0); } // update watched set watchedMovies = collectedMovies.Where(m => m.Watched > 0).ToList(); } #endregion #region Mark movies as watched in local database if (traktWatchedMovies != null && traktWatchedMovies.Count() > 0) { foreach (var twm in traktWatchedMovies) { var localMovie = collectedMovies.FirstOrDefault(m => MovieMatch(m, twm.Movie)); if (localMovie == null) { continue; } int iPercent; int iWatchedCount; bool localIsWatched = VideoDatabase.GetmovieWatchedStatus(localMovie.ID, out iPercent, out iWatchedCount); if (!localIsWatched || iWatchedCount < twm.Plays) { TraktLogger.Info("Updating local movie watched state / play count to match trakt.tv. Plays = '{0}', Title = '{1}', Year = '{2}', IMDb ID = '{3}', TMDb ID = '{4}'", twm.Plays, twm.Movie.Title, twm.Movie.Year.HasValue ? twm.Movie.Year.ToString() : "<empty>", twm.Movie.Ids.Imdb ?? "<empty>", twm.Movie.Ids.Tmdb.HasValue ? twm.Movie.Ids.Tmdb.ToString() : "<empty>"); if (localMovie.DateWatched == "0001-01-01 00:00:00") { DateTime dateWatched; if (DateTime.TryParse(twm.LastWatchedAt, out dateWatched)) { localMovie.DateWatched = dateWatched.ToString("yyyy-MM-dd HH:mm:ss"); } } localMovie.Watched = 1; localMovie.WatchedCount = twm.Plays; localMovie.WatchedPercent = iPercent; VideoDatabase.SetMovieWatchedCount(localMovie.ID, twm.Plays); VideoDatabase.SetMovieWatchedStatus(localMovie.ID, true, iPercent); IMDBMovie details = localMovie; VideoDatabase.SetMovieInfoById(localMovie.ID, ref details); } } } #endregion #region Add movies to watched history at trakt.tv if (traktWatchedMovies != null) { var syncWatchedMovies = new List <TraktSyncMovieWatched>(); TraktLogger.Info("Finding movies to add to trakt.tv watched history"); syncWatchedMovies = (from movie in watchedMovies where !traktWatchedMovies.ToList().Exists(c => MovieMatch(movie, c.Movie)) select new TraktSyncMovieWatched { Ids = new TraktMovieId { Imdb = movie.IMDBNumber.ToNullIfEmpty() }, Title = movie.Title, Year = movie.Year, WatchedAt = GetLastDateWatched(movie), }).ToList(); TraktLogger.Info("Adding {0} movies to trakt.tv watched history", syncWatchedMovies.Count); if (syncWatchedMovies.Count > 0) { // update local cache TraktCache.AddMoviesToWatchHistory(syncWatchedMovies); int pageSize = TraktSettings.SyncBatchSize; int pages = (int)Math.Ceiling((double)syncWatchedMovies.Count / pageSize); for (int i = 0; i < pages; i++) { TraktLogger.Info("Adding movies [{0}/{1}] to trakt.tv watched history", i + 1, pages); var pagedMovies = syncWatchedMovies.Skip(i * pageSize).Take(pageSize).ToList(); pagedMovies.ForEach(s => TraktLogger.Info("Adding movie to trakt.tv watched history. Title = '{0}', Year = '{1}', IMDb ID = '{2}', TMDb ID = '{3}', Date Watched = '{4}'", s.Title, s.Year.HasValue ? s.Year.ToString() : "<empty>", s.Ids.Imdb ?? "<empty>", s.Ids.Tmdb.HasValue ? s.Ids.Tmdb.ToString() : "<empty>", s.WatchedAt)); // remove title/year such that match against online ID only if (TraktSettings.SkipMoviesWithNoIdsOnSync) { pagedMovies.ForEach(m => { m.Title = null; m.Year = null; }); } var response = TraktAPI.TraktAPI.AddMoviesToWatchedHistory(new TraktSyncMoviesWatched { Movies = pagedMovies }); TraktLogger.LogTraktResponse <TraktSyncResponse>(response); // remove movies from cache which didn't succeed if (response != null && response.NotFound != null && response.NotFound.Movies.Count > 0) { TraktCache.RemoveMoviesFromWatchHistory(response.NotFound.Movies); } } } } #endregion #region Add movies to collection at trakt.tv if (traktCollectedMovies != null) { var syncCollectedMovies = new List <TraktSyncMovieCollected>(); TraktLogger.Info("Finding movies to add to trakt.tv collection"); syncCollectedMovies = (from movie in collectedMovies where !traktCollectedMovies.ToList().Exists(c => MovieMatch(movie, c.Movie)) select new TraktSyncMovieCollected { Ids = new TraktMovieId { Imdb = movie.IMDBNumber.ToNullIfEmpty() }, Title = movie.Title, Year = movie.Year, CollectedAt = movie.DateAdded.ToISO8601(), MediaType = GetMovieMediaType(movie), Resolution = GetMovieResolution(movie), AudioCodec = GetMovieAudioCodec(movie), AudioChannels = GetMovieAudioChannels(movie), Is3D = IsMovie3D(movie) }).ToList(); TraktLogger.Info("Adding {0} movies to trakt.tv collection", syncCollectedMovies.Count); if (syncCollectedMovies.Count > 0) { // update internal cache TraktCache.AddMoviesToCollection(syncCollectedMovies); int pageSize = TraktSettings.SyncBatchSize; int pages = (int)Math.Ceiling((double)syncCollectedMovies.Count / pageSize); for (int i = 0; i < pages; i++) { TraktLogger.Info("Adding movies [{0}/{1}] to trakt.tv collection", i + 1, pages); var pagedMovies = syncCollectedMovies.Skip(i * pageSize).Take(pageSize).ToList(); pagedMovies.ForEach(s => TraktLogger.Info("Adding movie to trakt.tv collection. Title = '{0}', Year = '{1}', IMDb ID = '{2}', TMDb ID = '{3}', Date Added = '{4}', MediaType = '{5}', Resolution = '{6}', Audio Codec = '{7}', Audio Channels = '{8}'", s.Title, s.Year.HasValue ? s.Year.ToString() : "<empty>", s.Ids.Imdb ?? "<empty>", s.Ids.Tmdb.HasValue ? s.Ids.Tmdb.ToString() : "<empty>", s.CollectedAt, s.MediaType ?? "<empty>", s.Resolution ?? "<empty>", s.AudioCodec ?? "<empty>", s.AudioChannels ?? "<empty>")); // remove title/year such that match against online ID only if (TraktSettings.SkipMoviesWithNoIdsOnSync) { pagedMovies.ForEach(m => { m.Title = null; m.Year = null; }); } var response = TraktAPI.TraktAPI.AddMoviesToCollecton(new TraktSyncMoviesCollected { Movies = pagedMovies }); TraktLogger.LogTraktResponse(response); // remove movies from cache which didn't succeed if (response != null && response.NotFound != null && response.NotFound.Movies.Count > 0) { TraktCache.RemoveMoviesFromCollection(response.NotFound.Movies); } } } } #endregion #region Remove movies no longer in collection from trakt.tv if (TraktSettings.KeepTraktLibraryClean && TraktSettings.MoviePluginCount == 1 && traktCollectedMovies != null) { var syncUnCollectedMovies = new List <TraktMovie>(); TraktLogger.Info("Finding movies to remove from trakt.tv collection"); // workout what movies that are in trakt collection that are not in local collection syncUnCollectedMovies = (from tcm in traktCollectedMovies where !collectedMovies.Exists(c => MovieMatch(c, tcm.Movie)) select new TraktMovie { Ids = tcm.Movie.Ids, Title = tcm.Movie.Title, Year = tcm.Movie.Year }).ToList(); TraktLogger.Info("Removing {0} movies from trakt.tv collection", syncUnCollectedMovies.Count); if (syncUnCollectedMovies.Count > 0) { // update local cache TraktCache.RemoveMoviesFromCollection(syncUnCollectedMovies); int pageSize = TraktSettings.SyncBatchSize; int pages = (int)Math.Ceiling((double)syncUnCollectedMovies.Count / pageSize); for (int i = 0; i < pages; i++) { TraktLogger.Info("Removing movies [{0}/{1}] from trakt.tv collection", i + 1, pages); var pagedMovies = syncUnCollectedMovies.Skip(i * pageSize).Take(pageSize).ToList(); pagedMovies.ForEach(s => TraktLogger.Info("Removing movie from trakt.tv collection, movie no longer exists locally. Title = '{0}', Year = '{1}', IMDb ID = '{2}', TMDb ID = '{3}'", s.Title, s.Year.HasValue ? s.Year.ToString() : "<empty>", s.Ids.Imdb ?? "<empty>", s.Ids.Tmdb.HasValue ? s.Ids.Tmdb.ToString() : "<empty>")); // remove title/year such that match against online ID only if (TraktSettings.SkipMoviesWithNoIdsOnSync) { pagedMovies.ForEach(m => { m.Title = null; m.Year = null; }); } var response = TraktAPI.TraktAPI.RemoveMoviesFromCollecton(new TraktSyncMovies { Movies = pagedMovies }); TraktLogger.LogTraktResponse(response); } } } #endregion } TraktLogger.Info("My Videos Library Sync Completed"); }