diff --git a/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs b/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs index 7c7a3e8ce..fea1cfefd 100644 --- a/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs +++ b/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs @@ -49,6 +49,7 @@ private void ValidateSeries(Series series) { series.Should().NotBeNull(); series.Title.Should().NotBeBlank(); + series.CleanTitle.Should().Be(Parser.Parser.CleanSeriesTitle(series.Title)); series.Overview.Should().NotBeBlank(); series.AirTime.Should().NotBeBlank(); series.FirstAired.Should().HaveValue(); diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 0526a672a..3fd954996 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -164,6 +164,7 @@ + diff --git a/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs b/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs new file mode 100644 index 000000000..6f8488c9f Binary files /dev/null and b/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs differ diff --git a/NzbDrone.Core/MetadataSource/TraktProxy.cs b/NzbDrone.Core/MetadataSource/TraktProxy.cs index 925549615..15cfc87d1 100644 --- a/NzbDrone.Core/MetadataSource/TraktProxy.cs +++ b/NzbDrone.Core/MetadataSource/TraktProxy.cs @@ -47,6 +47,7 @@ private static Series MapSeries(Show show) series.TvRageId = show.tvrage_id; series.ImdbId = show.imdb_id; series.Title = show.title; + series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.title); series.FirstAired = FromIso(show.first_aired_iso); series.Overview = show.overview; series.Runtime = show.runtime; diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index b4f1263f6..12a437e7e 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -343,6 +343,7 @@ + diff --git a/NzbDrone.Core/Tv/EpisodeService.cs b/NzbDrone.Core/Tv/EpisodeService.cs index d7cf318bf..1231d1384 100644 --- a/NzbDrone.Core/Tv/EpisodeService.cs +++ b/NzbDrone.Core/Tv/EpisodeService.cs @@ -30,6 +30,7 @@ public interface IEpisodeService List EpisodesBetweenDates(DateTime start, DateTime end); void InsertMany(List episodes); void UpdateMany(List episodes); + void DeleteMany(List episodes); } public class EpisodeService : IEpisodeService, @@ -170,6 +171,11 @@ public void UpdateMany(List episodes) _episodeRepository.UpdateMany(episodes); } + public void DeleteMany(List episodes) + { + _episodeRepository.DeleteMany(episodes); + } + public void HandleAsync(SeriesDeletedEvent message) { var episodes = GetEpisodeBySeries(message.Series.Id); diff --git a/NzbDrone.Core/Tv/RefreshEpisodeService.cs b/NzbDrone.Core/Tv/RefreshEpisodeService.cs new file mode 100644 index 000000000..c1f134c71 --- /dev/null +++ b/NzbDrone.Core/Tv/RefreshEpisodeService.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Tv.Events; + +namespace NzbDrone.Core.Tv +{ + public interface IRefreshEpisodeService + { + void RefreshEpisodeInfo(Series series, IEnumerable remoteEpisodes); + } + + public class RefreshEpisodeService : IRefreshEpisodeService + { + private readonly IEpisodeService _episodeService; + private readonly ISeasonService _seasonService; + private readonly IMessageAggregator _messageAggregator; + private readonly Logger _logger; + + public RefreshEpisodeService(IEpisodeService episodeService, + ISeasonService seasonService, IMessageAggregator messageAggregator, Logger logger) + { + _episodeService = episodeService; + _seasonService = seasonService; + _messageAggregator = messageAggregator; + _logger = logger; + } + + + public void RefreshEpisodeInfo(Series series, IEnumerable remoteEpisodes) + { + _logger.Info("Starting series info refresh for: {0}", series); + var successCount = 0; + var failCount = 0; + + var existinEpisodes = _episodeService.GetEpisodeBySeries(series.Id); + var seasons = _seasonService.GetSeasonsBySeries(series.Id); + + var updateList = new List(); + var newList = new List(); + + foreach (var episode in remoteEpisodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber)) + { + try + { + var episodeToUpdate = existinEpisodes.SingleOrDefault(e => e.TvDbEpisodeId == episode.TvDbEpisodeId) ?? + existinEpisodes.SingleOrDefault(e => e.SeasonNumber == episode.SeasonNumber && e.EpisodeNumber == episode.EpisodeNumber); + + if (episodeToUpdate != null) + { + existinEpisodes.Remove(episodeToUpdate); + updateList.Add(episodeToUpdate); + + if ((episodeToUpdate.EpisodeNumber != episode.EpisodeNumber || episodeToUpdate.SeasonNumber != episode.SeasonNumber) && episodeToUpdate.EpisodeFileId != 0) + { + _logger.Debug("Un-linking episode file because the episode number has changed"); + episodeToUpdate.EpisodeFileId = 0; + } + } + else + { + episodeToUpdate = new Episode(); + episodeToUpdate.Monitored = GetMonitoredStatus(episode, seasons); + newList.Add(episodeToUpdate); + } + + episodeToUpdate.SeriesId = series.Id; + episodeToUpdate.TvDbEpisodeId = episode.TvDbEpisodeId; + episodeToUpdate.EpisodeNumber = episode.EpisodeNumber; + episodeToUpdate.SeasonNumber = episode.SeasonNumber; + episodeToUpdate.Title = episode.Title; + episodeToUpdate.Overview = episode.Overview; + episodeToUpdate.AirDate = episode.AirDate; + episodeToUpdate.AirDateUtc = episode.AirDateUtc; + + successCount++; + } + catch (Exception e) + { + _logger.FatalException(String.Format("An error has occurred while updating episode info for series {0}. {1}", series, episode), e); + failCount++; + } + } + + var allEpisodes = new List(); + allEpisodes.AddRange(newList); + allEpisodes.AddRange(updateList); + + AdjustMultiEpisodeAirTime(series, allEpisodes); + + _episodeService.DeleteMany(existinEpisodes); + _episodeService.UpdateMany(updateList); + _episodeService.InsertMany(newList); + + + if (newList.Any()) + { + _messageAggregator.PublishEvent(new EpisodeInfoAddedEvent(newList, series)); + } + + if (updateList.Any()) + { + _messageAggregator.PublishEvent(new EpisodeInfoUpdatedEvent(updateList)); + } + + if (failCount != 0) + { + _logger.Info("Finished episode refresh for series: {0}. Successful: {1} - Failed: {2} ", + series.Title, successCount, failCount); + } + else + { + _logger.Info("Finished episode refresh for series: {0}.", series); + } + } + + private static bool GetMonitoredStatus(Episode episode, IEnumerable seasons) + { + if (episode.SeasonNumber == 0) + { + return false; + } + + if (episode.EpisodeNumber == 0 && episode.SeasonNumber != 1) + { + return false; + } + + var season = seasons.SingleOrDefault(c => c.SeasonNumber == episode.SeasonNumber); + return season == null || season.Monitored; + } + + private static void AdjustMultiEpisodeAirTime(Series series, IEnumerable allEpisodes) + { + var groups = + allEpisodes.Where(c => c.AirDateUtc.HasValue) + .GroupBy(e => new { e.SeriesId, e.AirDate }) + .Where(g => g.Count() > 1) + .ToList(); + + foreach (var group in groups) + { + var episodeCount = 0; + foreach (var episode in @group.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber)) + { + episode.AirDateUtc = episode.AirDateUtc.Value.AddMinutes(series.Runtime * episodeCount); + episodeCount++; + } + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Tv/RefreshSeriesService.cs b/NzbDrone.Core/Tv/RefreshSeriesService.cs index 7ad4c25e5..ac9517463 100644 --- a/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Common.Messaging; @@ -15,19 +14,16 @@ public class RefreshSeriesService : IExecute, IHandleAsync { private readonly IProvideSeriesInfo _seriesInfo; private readonly ISeriesService _seriesService; - private readonly IEpisodeService _episodeService; - private readonly ISeasonRepository _seasonRepository; + private readonly IRefreshEpisodeService _refreshEpisodeService; private readonly IMessageAggregator _messageAggregator; private readonly IDailySeriesService _dailySeriesService; private readonly Logger _logger; - public RefreshSeriesService(IProvideSeriesInfo seriesInfo, ISeriesService seriesService, IEpisodeService episodeService, - ISeasonRepository seasonRepository, IMessageAggregator messageAggregator, IDailySeriesService dailySeriesService, Logger logger) + public RefreshSeriesService(IProvideSeriesInfo seriesInfo, ISeriesService seriesService, IRefreshEpisodeService refreshEpisodeService, IMessageAggregator messageAggregator, IDailySeriesService dailySeriesService, Logger logger) { _seriesInfo = seriesInfo; _seriesService = seriesService; - _episodeService = episodeService; - _seasonRepository = seasonRepository; + _refreshEpisodeService = refreshEpisodeService; _messageAggregator = messageAggregator; _dailySeriesService = dailySeriesService; _logger = logger; @@ -74,7 +70,7 @@ private void RefreshSeriesInfo(Series series) series.AirTime = seriesInfo.AirTime; series.Overview = seriesInfo.Overview; series.Status = seriesInfo.Status; - series.CleanTitle = Parser.Parser.CleanSeriesTitle(seriesInfo.Title); + series.CleanTitle = seriesInfo.CleanTitle; series.LastInfoSync = DateTime.UtcNow; series.Runtime = seriesInfo.Runtime; series.Images = seriesInfo.Images; @@ -88,132 +84,11 @@ private void RefreshSeriesInfo(Series series) _seriesService.UpdateSeries(series); - RefreshEpisodeInfo(series, tuple.Item2); + _refreshEpisodeService.RefreshEpisodeInfo(series, tuple.Item2); _messageAggregator.PublishEvent(new SeriesUpdatedEvent(series)); } - private void RefreshEpisodeInfo(Series series, IEnumerable remoteEpisodes) - { - _logger.Info("Starting series info refresh for: {0}", series); - var successCount = 0; - var failCount = 0; - var seriesEpisodes = _episodeService.GetEpisodeBySeries(series.Id); - var seasons = _seasonRepository.GetSeasonBySeries(series.Id); - - var updateList = new List(); - var newList = new List(); - - foreach (var episode in remoteEpisodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber)) - { - try - { - var episodeToUpdate = seriesEpisodes.SingleOrDefault(e => e.TvDbEpisodeId == episode.TvDbEpisodeId) ?? - seriesEpisodes.SingleOrDefault(e => e.SeasonNumber == episode.SeasonNumber && e.EpisodeNumber == episode.EpisodeNumber); - - if (episodeToUpdate == null) - { - episodeToUpdate = new Episode(); - newList.Add(episodeToUpdate); - - //If it is Episode Zero or Season zero ignore it - if ((episode.EpisodeNumber == 0 && episode.SeasonNumber != 1) || episode.SeasonNumber == 0) - { - episodeToUpdate.Monitored = false; - } - else - { - var season = seasons.FirstOrDefault(c => c.SeasonNumber == episode.SeasonNumber); - episodeToUpdate.Monitored = season == null || season.Monitored; - } - } - else - { - updateList.Add(episodeToUpdate); - } - - if ((episodeToUpdate.EpisodeNumber != episode.EpisodeNumber || - episodeToUpdate.SeasonNumber != episode.SeasonNumber) && - episodeToUpdate.EpisodeFileId > 0) - { - _logger.Debug("Un-linking episode file because the episode number has changed"); - episodeToUpdate.EpisodeFileId = 0; - } - - episodeToUpdate.SeriesId = series.Id; - episodeToUpdate.TvDbEpisodeId = episode.TvDbEpisodeId; - episodeToUpdate.EpisodeNumber = episode.EpisodeNumber; - episodeToUpdate.SeasonNumber = episode.SeasonNumber; - episodeToUpdate.Title = episode.Title; - episodeToUpdate.Overview = episode.Overview; - episodeToUpdate.AirDate = episode.AirDate; - episodeToUpdate.AirDateUtc = episode.AirDateUtc; - - successCount++; - } - catch (Exception e) - { - _logger.FatalException(String.Format("An error has occurred while updating episode info for series {0}. {1}", series, episode), e); - failCount++; - } - } - - var allEpisodes = new List(); - allEpisodes.AddRange(newList); - allEpisodes.AddRange(updateList); - - var groups = allEpisodes.Where(c=>c.AirDateUtc.HasValue).GroupBy(e => new { e.SeriesId, e.AirDate }).Where(g => g.Count() > 1).ToList(); - - foreach (var group in groups) - { - int episodeCount = 0; - foreach (var episode in group.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber)) - { - episode.AirDateUtc = episode.AirDateUtc.Value.AddMinutes(series.Runtime * episodeCount); - episodeCount++; - } - } - - _episodeService.UpdateMany(updateList); - _episodeService.InsertMany(newList); - - if (newList.Any()) - { - _messageAggregator.PublishEvent(new EpisodeInfoAddedEvent(newList, series)); - } - - if (updateList.Any()) - { - _messageAggregator.PublishEvent(new EpisodeInfoUpdatedEvent(updateList)); - } - - if (failCount != 0) - { - _logger.Info("Finished episode refresh for series: {0}. Successful: {1} - Failed: {2} ", - series.Title, successCount, failCount); - } - else - { - _logger.Info("Finished episode refresh for series: {0}.", series); - } - - //DeleteEpisodesNotAvailableAnymore(series, remoteEpisodes); - } - - - /* private void DeleteEpisodesNotAvailableAnymore(Series series, IEnumerable onlineEpisodes) - { - //Todo: This will not work as currently implemented - what are we trying to do here? - //Todo: We were trying to remove episodes that were once on tvdb but were removed, for whatever reason, instead of polluting our DB with them. - return; - _logger.Trace("Starting deletion of episodes that no longer exist in TVDB: {0}", series.Title.WithDefault(series.Id)); - foreach (var episode in onlineEpisodes) - { - _episodeRepository.Delete(episode.Id); - } - - _logger.Trace("Deleted episodes that no longer exist in TVDB for {0}", series.Id); - }*/ } } \ No newline at end of file diff --git a/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj b/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj index abf8ad677..171f9df7d 100644 --- a/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj +++ b/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj @@ -51,6 +51,10 @@ ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll + + False + ..\packages\Newtonsoft.Json.5.0.3\lib\net35\Newtonsoft.Json.dll + False ..\packages\NLog.2.0.1.2\lib\net40\NLog.dll @@ -76,6 +80,7 @@ + diff --git a/NzbDrone.Test.Common/ObjectExtentions.cs b/NzbDrone.Test.Common/ObjectExtentions.cs new file mode 100644 index 000000000..e6ea1e49e --- /dev/null +++ b/NzbDrone.Test.Common/ObjectExtentions.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace NzbDrone.Test.Common +{ + public static class ObjectExtentions + { + public static T JsonClone(this T source) + { + var json = JsonConvert.SerializeObject(source); + return JsonConvert.DeserializeObject(json); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Test.Common/StringExtensions.cs b/NzbDrone.Test.Common/StringExtensions.cs index 7ec3f4698..7a7a420de 100644 --- a/NzbDrone.Test.Common/StringExtensions.cs +++ b/NzbDrone.Test.Common/StringExtensions.cs @@ -1,4 +1,5 @@ using System.IO; +using Newtonsoft.Json.Serialization; using NzbDrone.Common.EnvironmentInfo; namespace NzbDrone.Test.Common diff --git a/NzbDrone.Test.Common/packages.config b/NzbDrone.Test.Common/packages.config index df8a9c997..11b24f356 100644 --- a/NzbDrone.Test.Common/packages.config +++ b/NzbDrone.Test.Common/packages.config @@ -3,6 +3,7 @@ + diff --git a/NzbDrone/NzbDrone.csproj b/NzbDrone/NzbDrone.csproj index d3551c601..2ed8652b8 100644 --- a/NzbDrone/NzbDrone.csproj +++ b/NzbDrone/NzbDrone.csproj @@ -106,10 +106,6 @@ False ..\packages\Nancy.Owin.0.16.1\lib\net40\Nancy.Owin.dll - - False - ..\packages\Newtonsoft.Json.4.5.11\lib\net35\Newtonsoft.Json.dll - False ..\packages\NLog.2.0.1.2\lib\net40\NLog.dll diff --git a/NzbDrone/packages.config b/NzbDrone/packages.config index c6d386832..3ded934ee 100644 --- a/NzbDrone/packages.config +++ b/NzbDrone/packages.config @@ -8,7 +8,7 @@ - +