From 30ffe79442cf4505be8870cc4b6d59664e8cf6f3 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 29 Sep 2011 21:40:00 -0700 Subject: [PATCH] DeleteInvalidEpisodes with tests added to delete episodes that TheTvDb no longer has (previously bad data). --- ...isodeProviderTest_DeleteInvalidEpisodes.cs | 323 ++++++++++++++++++ NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 1 + NzbDrone.Core/Providers/EpisodeProvider.cs | 37 ++ NzbDrone.Core/Providers/SeriesProvider.cs | 2 +- readme.md | 1 + 5 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 NzbDrone.Core.Test/EpisodeProviderTest_DeleteInvalidEpisodes.cs diff --git a/NzbDrone.Core.Test/EpisodeProviderTest_DeleteInvalidEpisodes.cs b/NzbDrone.Core.Test/EpisodeProviderTest_DeleteInvalidEpisodes.cs new file mode 100644 index 000000000..0e6b98707 --- /dev/null +++ b/NzbDrone.Core.Test/EpisodeProviderTest_DeleteInvalidEpisodes.cs @@ -0,0 +1,323 @@ +// ReSharper disable RedundantUsingDirective +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMoq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Core; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; +using PetaPoco; +using TvdbLib.Data; + +namespace NzbDrone.Core.Test +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class EpisodeProviderTest_DeleteInvalidEpisodes : TestBase + { + [Test] + public void Delete_None() + { + //Setup + const int seriesId = 71663; + const int episodeCount = 10; + + var tvDbSeries = Builder.CreateNew().With( + c => c.Episodes = + new List(Builder.CreateListOfSize(episodeCount). + WhereAll() + .Have(l => l.Language = new TvdbLanguage(0, "eng", "a")) + .Build()) + ).With(c => c.Id = seriesId).Build(); + + var fakeSeries = Builder.CreateNew() + .With(c => c.SeriesId = seriesId) + .Build(); + + var fakeEpisode = Builder.CreateNew() + .With(e => e.SeriesId = seriesId) + .With(e => e.SeasonNumber = 20) + .With(e => e.EpisodeNumber = 20) + .Build(); + + var mocker = new AutoMoqer(); + + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + db.Insert(fakeSeries); + db.Insert(fakeEpisode); + + //Act + mocker.Resolve().DeleteInvalidEpisodes(fakeSeries, tvDbSeries); + + //Assert + var result = db.Fetch(); + result.Should().HaveCount(1); + } + + [Test] + public void Delete_TvDbId() + { + //Setup + const int seriesId = 71663; + const int episodeCount = 10; + + var tvDbSeries = Builder.CreateNew().With( + c => c.Episodes = + new List(Builder.CreateListOfSize(episodeCount). + WhereAll() + .Have(l => l.Language = new TvdbLanguage(0, "eng", "a")) + .Build()) + ).With(c => c.Id = seriesId).Build(); + + var fakeSeries = Builder.CreateNew() + .With(c => c.SeriesId = seriesId) + .Build(); + + var fakeEpisode = Builder.CreateNew() + .With(e => e.SeriesId = seriesId) + .With(e => e.SeasonNumber = 20) + .With(e => e.EpisodeNumber = 20) + .With(e => e.TvDbEpisodeId = 300) + .Build(); + + var mocker = new AutoMoqer(); + + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + db.Insert(fakeSeries); + db.Insert(fakeEpisode); + + //Act + mocker.Resolve().DeleteInvalidEpisodes(fakeSeries, tvDbSeries); + + //Assert + var result = db.Fetch(); + result.Should().HaveCount(0); + } + + [Test] + public void Delete_EpisodeNumber() + { + //Setup + const int seriesId = 71663; + const int episodeCount = 10; + + var tvDbSeries = Builder.CreateNew().With( + c => c.Episodes = + new List(Builder.CreateListOfSize(episodeCount). + WhereAll() + .Have(l => l.Language = new TvdbLanguage(0, "eng", "a")) + .Build()) + ).With(c => c.Id = seriesId).Build(); + + var fakeSeries = Builder.CreateNew() + .With(c => c.SeriesId = seriesId) + .Build(); + + var fakeEpisode = Builder.CreateNew() + .With(e => e.SeriesId = seriesId) + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumber = 20) + .With(e => e.TvDbEpisodeId = 1) + .Build(); + + var mocker = new AutoMoqer(); + + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + db.Insert(fakeSeries); + db.Insert(fakeEpisode); + + //Act + mocker.Resolve().DeleteInvalidEpisodes(fakeSeries, tvDbSeries); + + //Assert + var result = db.Fetch(); + result.Should().HaveCount(0); + } + + [Test] + public void Delete_Both() + { + //Setup + const int seriesId = 71663; + const int episodeCount = 10; + + var tvDbSeries = Builder.CreateNew().With( + c => c.Episodes = + new List(Builder.CreateListOfSize(episodeCount). + WhereAll() + .Have(l => l.Language = new TvdbLanguage(0, "eng", "a")) + .Build()) + ).With(c => c.Id = seriesId).Build(); + + var fakeSeries = Builder.CreateNew() + .With(c => c.SeriesId = seriesId) + .Build(); + + var fakeEpisode1 = Builder.CreateNew() + .With(e => e.SeriesId = seriesId) + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumber = 20) + .With(e => e.TvDbEpisodeId = 1) + .Build(); + + var fakeEpisode2 = Builder.CreateNew() + .With(e => e.SeriesId = seriesId) + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumber = 1) + .With(e => e.TvDbEpisodeId = 300) + .Build(); + + //This should not be deleted + var fakeEpisode3 = Builder.CreateNew() + .With(e => e.SeriesId = seriesId) + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumber = 1) + .With(e => e.TvDbEpisodeId = 1) + .With(e => e.Title = "Not Deleted") + .Build(); + + var mocker = new AutoMoqer(); + + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + db.Insert(fakeSeries); + db.Insert(fakeEpisode1); + db.Insert(fakeEpisode2); + db.Insert(fakeEpisode3); + + //Act + mocker.Resolve().DeleteInvalidEpisodes(fakeSeries, tvDbSeries); + + //Assert + var result = db.Fetch(); + result.Should().HaveCount(1); + result.First().Title.Should().Be("Not Deleted"); + } + + //Other series, by season/episode + by tvdbid + [Test] + public void Delete_TvDbId_multiple_series() + { + //Setup + const int seriesId = 71663; + const int episodeCount = 10; + + var tvDbSeries = Builder.CreateNew().With( + c => c.Episodes = + new List(Builder.CreateListOfSize(episodeCount). + WhereAll() + .Have(l => l.Language = new TvdbLanguage(0, "eng", "a")) + .Build()) + ).With(c => c.Id = seriesId).Build(); + + var fakeSeries = Builder.CreateNew() + .With(c => c.SeriesId = seriesId) + .Build(); + + var fakeEpisode = Builder.CreateNew() + .With(e => e.SeriesId = seriesId) + .With(e => e.SeasonNumber = 20) + .With(e => e.EpisodeNumber = 20) + .With(e => e.TvDbEpisodeId = 300) + .Build(); + + //Other Series + var otherFakeSeries = Builder.CreateNew() + .With(c => c.SeriesId = 12345) + .Build(); + + var otherFakeEpisode = Builder.CreateNew() + .With(e => e.SeriesId = 12345) + .With(e => e.SeasonNumber = 20) + .With(e => e.EpisodeNumber = 20) + .With(e => e.TvDbEpisodeId = 300) + .Build(); + + var mocker = new AutoMoqer(); + + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + db.Insert(fakeSeries); + db.Insert(fakeEpisode); + db.Insert(otherFakeSeries); + db.Insert(otherFakeEpisode); + + //Act + mocker.Resolve().DeleteInvalidEpisodes(fakeSeries, tvDbSeries); + + //Assert + var result = db.Fetch(); + result.Should().HaveCount(1); + } + + [Test] + public void Delete_EpisodeNumber_multiple_series() + { + //Setup + const int seriesId = 71663; + const int episodeCount = 10; + + var tvDbSeries = Builder.CreateNew().With( + c => c.Episodes = + new List(Builder.CreateListOfSize(episodeCount). + WhereAll() + .Have(l => l.Language = new TvdbLanguage(0, "eng", "a")) + .Build()) + ).With(c => c.Id = seriesId).Build(); + + var fakeSeries = Builder.CreateNew() + .With(c => c.SeriesId = seriesId) + .Build(); + + var fakeEpisode = Builder.CreateNew() + .With(e => e.SeriesId = seriesId) + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumber = 20) + .With(e => e.TvDbEpisodeId = 1) + .Build(); + + //Other Series + var otherFakeSeries = Builder.CreateNew() + .With(c => c.SeriesId = 12345) + .Build(); + + var otherFakeEpisode = Builder.CreateNew() + .With(e => e.SeriesId = 12345) + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumber = 4) + .With(e => e.TvDbEpisodeId = 2) + .Build(); + + var mocker = new AutoMoqer(); + + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + db.Insert(fakeSeries); + db.Insert(fakeEpisode); + db.Insert(otherFakeSeries); + db.Insert(otherFakeEpisode); + + //Act + mocker.Resolve().DeleteInvalidEpisodes(fakeSeries, tvDbSeries); + + //Assert + var result = db.Fetch(); + result.Should().HaveCount(1); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 859d80822..a63ff4f5d 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -90,6 +90,7 @@ + diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index aac994441..d7cbb7009 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -6,6 +6,7 @@ using NLog; using NzbDrone.Core.Model; using NzbDrone.Core.Repository; using PetaPoco; +using TvdbLib.Data; namespace NzbDrone.Core.Providers { @@ -262,6 +263,9 @@ namespace NzbDrone.Core.Providers Logger.Info("Finished episode refresh for series: {0}. Successful: {1} - Failed: {2} ", tvDbSeriesInfo.SeriesName, successCount, failCount); + + //DeleteInvalidEpisodes + DeleteInvalidEpisodes(series, tvDbSeriesInfo); } public virtual void UpdateEpisode(Episode episode) @@ -364,5 +368,38 @@ namespace NzbDrone.Core.Providers episode.Series = _seriesProvider.GetSeries(episode.SeriesId); return episode; } + + public virtual void DeleteInvalidEpisodes(Series series, TvdbSeries tvDbSeriesInfo) + { + Logger.Info("Starting deletion of invalid episode for series: {0}", series.Title.WithDefault(series.SeriesId)); + + var seasons = tvDbSeriesInfo.Episodes.Select(e => e.SeasonNumber).Distinct(); + + foreach (var s in seasons) + { + //Avoiding accessing modified closure + var season = s; + Logger.Trace("Processing invalid episodes for {0}, Season: {1}", series.SeriesId, season); + + var episodesInSeason = tvDbSeriesInfo.Episodes.Where(e => e.SeasonNumber == season).Select(e => e.EpisodeNumber); + var episodesString = String.Join(", ", episodesInSeason); + var seasonQuery = String.Format("DELETE FROM Episodes WHERE SeriesId = {0} AND SeasonNumber = {1} AND EpisodeNumber NOT IN ({2})", + series.SeriesId, season, episodesString); + + _database.Execute(seasonQuery); + } + + //Delete Episodes not matching TvDbIds for this series + var tvDbIds = tvDbSeriesInfo.Episodes.Select(e => e.Id); + var tvDbIdString = String.Join(", ", tvDbIds); + + var tvDbIdQuery = String.Format("DELETE FROM Episodes WHERE SeriesId = {0} AND TvDbEpisodeId > 0 AND TvDbEpisodeId NOT IN ({1})", + series.SeriesId, tvDbIdString); + + Logger.Trace("Deleting nivalid episodes by TvDbId for {0}", series.SeriesId); + _database.Execute(tvDbIdQuery); + + Logger.Trace("Finished deleting invalid episodes for {0}", series.SeriesId); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/SeriesProvider.cs b/NzbDrone.Core/Providers/SeriesProvider.cs index 3805b82bc..e6ecc90da 100644 --- a/NzbDrone.Core/Providers/SeriesProvider.cs +++ b/NzbDrone.Core/Providers/SeriesProvider.cs @@ -82,7 +82,7 @@ namespace NzbDrone.Core.Providers public virtual Series UpdateSeriesInfo(int seriesId) { - var tvDbSeries = _tvDbProvider.GetSeries(seriesId, true); + var tvDbSeries = _tvDbProvider.GetSeries(seriesId, false); var series = GetSeries(seriesId); series.SeriesId = tvDbSeries.Id; diff --git a/readme.md b/readme.md index 2a799291b..55ffc0351 100644 --- a/readme.md +++ b/readme.md @@ -31,6 +31,7 @@ NZBDrone makes use of the following projects: * [MVC Mini Profiler](http://code.google.com/p/mvc-mini-profiler/) * [Migrator.NET](https://github.com/kayone/Migrator.NET) * [Telerik Extensions for ASP.NET MVC](http://www.telerik.com/products/aspnet-mvc.aspx) + ## Development Tools * [Visual Studio 2010](http://www.microsoft.com/visualstudio/en-us/products/2010-editions) * [ReSharper 6](http://www.jetbrains.com/resharper/index.html)