From 55beec49082b50430b1cb9eca519a53a93187b4d Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 10 Jul 2014 19:23:57 -0700 Subject: [PATCH] Fixed: Attempt to refresh anime episodes by absolute numering when refreshing --- .../TvTests/RefreshEpisodeServiceFixture.cs | 115 ++++++++++++++++-- .../TvTests/RefreshSeriesServiceFixture.cs | 20 ++- src/NzbDrone.Core/Tv/RefreshEpisodeService.cs | 61 +++++++--- 3 files changed, 164 insertions(+), 32 deletions(-) diff --git a/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs b/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs index 8ef10beac..e38470579 100644 --- a/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Core.MetadataSource; +using NzbDrone.Core.MetadataSource.Tvdb; using NzbDrone.Core.Tv; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; @@ -42,6 +43,15 @@ namespace NzbDrone.Core.Test.TvTests return series; } + private Series GetAnimeSeries() + { + var series = Builder.CreateNew().Build(); + series.SeriesType = SeriesTypes.Anime; + series.Seasons = new List(); + + return series; + } + [SetUp] public void Setup() { @@ -61,6 +71,13 @@ namespace NzbDrone.Core.Test.TvTests .Callback>(e => _deletedEpisodes = e); } + private void GivenAnimeEpisodes(List episodes) + { + Mocker.GetMock() + .Setup(s => s.GetEpisodeInfo(It.IsAny())) + .Returns(episodes); + } + [Test] public void should_create_all_when_no_existing_episodes() { @@ -168,24 +185,102 @@ namespace NzbDrone.Core.Test.TvTests } [Test] - [Ignore] - public void should_set_absolute_episode_number() + public void should_set_absolute_episode_number_for_anime() { - //TODO: Only run this against an anime series + var episodes = Builder.CreateListOfSize(3).Build().ToList(); + GivenAnimeEpisodes(episodes); Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) .Returns(new List()); - Subject.RefreshEpisodeInfo(GetSeries(), GetEpisodes()); + Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); - var season1 = _insertedEpisodes.Where(e => e.SeasonNumber == 1 && e.EpisodeNumber > 0); - var season2episode1 = _insertedEpisodes.Single(e => e.SeasonNumber == 2 && e.EpisodeNumber == 1); - - season2episode1.AbsoluteEpisodeNumber.Should().Be(season1.Count() + 1); - - _insertedEpisodes.Where(e => e.SeasonNumber > 0 && e.EpisodeNumber > 0).All(e => e.AbsoluteEpisodeNumber > 0).Should().BeTrue(); + _insertedEpisodes.All(e => e.AbsoluteEpisodeNumber > 0).Should().BeTrue(); _updatedEpisodes.Should().BeEmpty(); _deletedEpisodes.Should().BeEmpty(); } + + [Test] + public void should_set_absolute_episode_number_even_if_not_previously_set_for_anime() + { + var episodes = Builder.CreateListOfSize(3).Build().ToList(); + GivenAnimeEpisodes(episodes); + + var existingEpisodes = episodes.JsonClone(); + existingEpisodes.ForEach(e => e.AbsoluteEpisodeNumber = 0); + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(existingEpisodes); + + Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); + + _insertedEpisodes.Should().BeEmpty(); + _updatedEpisodes.All(e => e.AbsoluteEpisodeNumber > 0).Should().BeTrue(); + _deletedEpisodes.Should().BeEmpty(); + } + + [Test] + public void should_get_new_season_and_episode_numbers_when_absolute_episode_number_match_found() + { + const Int32 expectedSeasonNumber = 10; + const Int32 expectedEpisodeNumber = 5; + const Int32 expectedAbsoluteNumber = 3; + + var episode = Builder.CreateNew() + .With(e => e.SeasonNumber = expectedSeasonNumber) + .With(e => e.EpisodeNumber = expectedEpisodeNumber) + .With(e => e.AbsoluteEpisodeNumber = expectedAbsoluteNumber) + .Build(); + + GivenAnimeEpisodes(new List { episode }); + + var existingEpisode = episode.JsonClone(); + existingEpisode.SeasonNumber = 1; + existingEpisode.EpisodeNumber = 1; + existingEpisode.AbsoluteEpisodeNumber = 1; + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List{ existingEpisode }); + + Subject.RefreshEpisodeInfo(GetAnimeSeries(), new List { episode }); + + _insertedEpisodes.Should().BeEmpty(); + _deletedEpisodes.Should().BeEmpty(); + + _updatedEpisodes.First().SeasonNumber.Should().Be(expectedSeasonNumber); + _updatedEpisodes.First().EpisodeNumber.Should().Be(expectedEpisodeNumber); + _updatedEpisodes.First().AbsoluteEpisodeNumber.Should().Be(expectedAbsoluteNumber); + } + + [Test] + public void should_prefer_absolute_match_over_season_and_epsiode_match() + { + var episodes = Builder.CreateListOfSize(2) + .Build() + .ToList(); + + episodes[0].AbsoluteEpisodeNumber = 0; + episodes[0].SeasonNumber.Should().NotBe(episodes[1].SeasonNumber); + episodes[0].EpisodeNumber.Should().NotBe(episodes[1].EpisodeNumber); + episodes[0].AbsoluteEpisodeNumber.Should().NotBe(episodes[1].AbsoluteEpisodeNumber); + + GivenAnimeEpisodes(episodes); + + var existingEpisode = new Episode + { + SeasonNumber = episodes[0].SeasonNumber, + EpisodeNumber = episodes[0].EpisodeNumber, + AbsoluteEpisodeNumber = episodes[1].AbsoluteEpisodeNumber + }; + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List { existingEpisode }); + + Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); + + _updatedEpisodes.First().SeasonNumber.Should().Be(episodes[1].SeasonNumber); + _updatedEpisodes.First().EpisodeNumber.Should().Be(episodes[1].EpisodeNumber); + _updatedEpisodes.First().AbsoluteEpisodeNumber.Should().Be(episodes[1].AbsoluteEpisodeNumber); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs b/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs index 7e263b235..ea948dd67 100644 --- a/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs @@ -49,12 +49,12 @@ namespace NzbDrone.Core.Test.TvTests [Test] public void should_monitor_new_seasons_automatically() { - var series = _series.JsonClone(); - series.Seasons.Add(Builder.CreateNew() + var newSeriesInfo = _series.JsonClone(); + newSeriesInfo.Seasons.Add(Builder.CreateNew() .With(s => s.SeasonNumber = 2) .Build()); - GivenNewSeriesInfo(series); + GivenNewSeriesInfo(newSeriesInfo); Subject.Execute(new RefreshSeriesCommand(_series.Id)); @@ -77,5 +77,19 @@ namespace NzbDrone.Core.Test.TvTests Mocker.GetMock() .Verify(v => v.UpdateSeries(It.Is(s => s.Seasons.Count == 2 && s.Seasons.Single(season => season.SeasonNumber == 0).Monitored == false))); } + + [Test] + public void should_update_tvrage_id_if_changed() + { + var newSeriesInfo = _series.JsonClone(); + newSeriesInfo.TvRageId = _series.TvRageId + 1; + + GivenNewSeriesInfo(newSeriesInfo); + + Subject.Execute(new RefreshSeriesCommand(_series.Id)); + + Mocker.GetMock() + .Verify(v => v.UpdateSeries(It.Is(s => s.TvRageId == newSeriesInfo.TvRageId))); + } } } diff --git a/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs b/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs index 190538b48..2bd7c901c 100644 --- a/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs +++ b/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs @@ -42,11 +42,16 @@ namespace NzbDrone.Core.Tv var newList = new List(); var dupeFreeRemoteEpisodes = remoteEpisodes.DistinctBy(m => new { m.SeasonNumber, m.EpisodeNumber }).ToList(); - foreach (var episode in dupeFreeRemoteEpisodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber)) + if (series.SeriesType == SeriesTypes.Anime) + { + dupeFreeRemoteEpisodes = MapAbsoluteEpisodeNumbers(series, dupeFreeRemoteEpisodes); + } + + foreach (var episode in OrderEpsiodes(series, dupeFreeRemoteEpisodes)) { try { - var episodeToUpdate = existingEpisodes.FirstOrDefault(e => e.SeasonNumber == episode.SeasonNumber && e.EpisodeNumber == episode.EpisodeNumber); + var episodeToUpdate = GetEpisodeToUpdate(series, episode, existingEpisodes); if (episodeToUpdate != null) { @@ -63,6 +68,7 @@ namespace NzbDrone.Core.Tv episodeToUpdate.SeriesId = series.Id; episodeToUpdate.EpisodeNumber = episode.EpisodeNumber; episodeToUpdate.SeasonNumber = episode.SeasonNumber; + episodeToUpdate.AbsoluteEpisodeNumber = episode.AbsoluteEpisodeNumber; episodeToUpdate.Title = episode.Title; episodeToUpdate.Overview = episode.Overview; episodeToUpdate.AirDate = episode.AirDate; @@ -70,13 +76,6 @@ namespace NzbDrone.Core.Tv episodeToUpdate.Ratings = episode.Ratings; episodeToUpdate.Images = episode.Images; - //Reset the absolute episode number to zero if the series is not anime - if (series.SeriesType != SeriesTypes.Anime) - { - episodeToUpdate.AbsoluteEpisodeNumber = 0; - } - - successCount++; } catch (Exception e) @@ -91,7 +90,6 @@ namespace NzbDrone.Core.Tv allEpisodes.AddRange(updateList); AdjustMultiEpisodeAirTime(series, allEpisodes); - SetAbsoluteEpisodeNumber(series, allEpisodes); _episodeService.DeleteMany(existingEpisodes); _episodeService.UpdateMany(updateList); @@ -153,18 +151,11 @@ namespace NzbDrone.Core.Tv } } - private void SetAbsoluteEpisodeNumber(Series series, IEnumerable allEpisodes) + private List MapAbsoluteEpisodeNumbers(Series series, List traktEpisodes) { - if (series.SeriesType != SeriesTypes.Anime) - { - _logger.Debug("Skipping absolute number lookup for non-anime"); - - return; - } - var tvdbEpisodes = _tvdbProxy.GetEpisodeInfo(series.TvdbId); - foreach (var episode in allEpisodes) + foreach (var episode in traktEpisodes) { //I'd use single, but then I'd have to trust the tvdb data... and I don't var tvdbEpisode = tvdbEpisodes.FirstOrDefault(e => e.SeasonNumber == episode.SeasonNumber && @@ -178,6 +169,38 @@ namespace NzbDrone.Core.Tv episode.AbsoluteEpisodeNumber = tvdbEpisode.AbsoluteEpisodeNumber; } + + return traktEpisodes.DistinctBy(e => e.AbsoluteEpisodeNumber).ToList(); + } + + private Episode GetEpisodeToUpdate(Series series, Episode episode, IEnumerable existingEpisodes) + { + if (series.SeriesType == SeriesTypes.Anime) + { + if (episode.AbsoluteEpisodeNumber > 0) + { + return existingEpisodes.FirstOrDefault(e => e.AbsoluteEpisodeNumber == episode.AbsoluteEpisodeNumber); + } + } + + return existingEpisodes.FirstOrDefault(e => e.SeasonNumber == episode.SeasonNumber && e.EpisodeNumber == episode.EpisodeNumber); + } + + private IEnumerable OrderEpsiodes(Series series, List episodes) + { + if (series.SeriesType == SeriesTypes.Anime) + { + var withAbs = episodes.Where(e => e.AbsoluteEpisodeNumber > 0) + .OrderBy(e => e.AbsoluteEpisodeNumber); + + var withoutAbs = episodes.Where(e => e.AbsoluteEpisodeNumber == 0) + .OrderBy(e => e.SeasonNumber) + .ThenBy(e => e.EpisodeNumber); + + return withAbs.Concat(withoutAbs); + } + + return episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber); } } } \ No newline at end of file