From 6f9d7d83d4d2ce25e9d52e5be1b3df4f09a55b58 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 16 Oct 2012 17:08:40 -0700 Subject: [PATCH 01/13] Revert "Updated XemClient" This reverts commit 0de4a43cc109e0d866af3a99971b32b7a54ba706. --- Libraries/XemLib/XemLib.dll | Bin 33280 -> 33280 bytes Libraries/XemLib/XemLib.pdb | Bin 60928 -> 60928 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Libraries/XemLib/XemLib.dll b/Libraries/XemLib/XemLib.dll index 465036a6c3d77b345558444dcc98aba544c263ff..b32ae3128c86729098a1e21c4de42858d6bfd870 100644 GIT binary patch delta 117 zcmV-*0E+*BgaUws0+5ITiQ}<~Q5zZc000I81B@#G00Age000UqMslJE0B|Vvv-BIv zLJ>q_LSb`dRdQ?qb7gd2RCYv@&t6OcM3Wp}HvvSmR9`$25N}t$r@MF#OrareKO+`8 X*R!B;lm`NdFa delta 104 zcmV-u0GI!OgaUws0+5IT3FfhiQ5%y591#dca-s+Ta47S$^c%`T5oBUQVRL0wa%=!| zWprOuc4U*!UQ7XGlN?_+0c5jOUpx{JD4&bj5os|=uTKAdl}_Wtv!HR52LcJ^lQwxO K0S2>Sd7lXCU?-da diff --git a/Libraries/XemLib/XemLib.pdb b/Libraries/XemLib/XemLib.pdb index b65f156ffa84cc59cf805e19187e7b626c1b3d4a..a71ae0ff84132b0a36c7419215581a5ed49f01cb 100644 GIT binary patch delta 843 zcmX|bB?mJO{Zj-N^CmIhaxJshH0C& z>47LJwx-ZXv(+t)d{F5jOLUv_O+hITkzj&Qgi@WmnLqfQ^SS4I&$;J!f7iT7&5P8z zUnUBIa2;ROn70M2-m3>?H&JYPpZX6Y1>XeeLUT%pYM$j5u#0vgW1tb`4NbP48 zFl4;9P3mS&{^llB9&On~E;JdCU8e8I)>acSvW?akBr0dVYw;Xi8MPn4b zwh_!-wBx?uw@-@(8d3PVcNEH=k^j*jJv-5w2fQxTF-EM+@5trb_#KCsviGBu7}&zY zzXTgWhu663(dM`nFy*=sA#4K+Zhk1IpN}I`&nVh4ZdqG@ zla$wbGx#@cyxioi!$bVk^shdn-8ak&!@~-2594M?*q&9sri3(t)_0hcGplcq7w5CV zow?pc=6bJAqy_lM|4?BYEkq;lnaISp^`{&P^nETOUOC;YGkYaC$h<)Cl0n~ctPUj%+-*{qxH0m z`5RLbFgbIhK#27brwop0+?b2p!hTlPWWq3;F`7l_^v&t~&eW31cf&m&>4%E@9$`oT zK~bS&}G5hwrE%3hA5-8slNMh0bI8^ptqP@a{%qJ-!` zwSE2f56{RJxMo&vM~618E@?B4=Ofqcqg>|0TFnPYeZrQiRG$sr)K n9=!P?@Xm^oQ}Ev~;Ht;f00FLqBtx$}2efH+_xv*H`z87hH0N}@ From 4b5d20cefe6b18481a96a14da8f26a203cf02d32 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 16 Oct 2012 17:08:47 -0700 Subject: [PATCH 02/13] Revert "XemClient added" This reverts commit a6e8ec6123d3095652c8c30539f2822676e682fb. --- Libraries/XemLib/XemLib.dll | Bin 33280 -> 0 bytes Libraries/XemLib/XemLib.pdb | Bin 60928 -> 0 bytes NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 7 +- .../ProviderTests/EpisodeProviderTest.cs | 52 +++++++------- ...isodeProviderTest_DeleteInvalidEpisodes.cs | 12 ++-- .../Metadata/Xbmc_ForEpisodeFile_Fixture.cs | 24 +++---- .../Metadata/Xbmc_ForSeries_Fixture.cs | 36 +++++----- .../RecycleBinProviderTests/CleanupFixture.cs | 1 + .../DeleteDirectoryFixture.cs | 1 + .../DeleteFileFixture.cs | 1 + .../RecycleBinProviderTests/EmptyFixture.cs | 1 + .../ProviderTests/SeasonProviderTest.cs | 1 + .../ProviderTests/TvDbProviderTest.cs | 59 +++++++++++++++- NzbDrone.Core/NzbDrone.Core.csproj | 7 +- NzbDrone.Core/Providers/EpisodeProvider.cs | 2 +- .../Providers/Metadata/MetadataBase.cs | 2 +- NzbDrone.Core/Providers/Metadata/Xbmc.cs | 34 ++++----- NzbDrone.Core/Providers/MetadataProvider.cs | 8 +-- NzbDrone.Core/Providers/SeriesProvider.cs | 4 +- NzbDrone.Core/Providers/TvDbProvider.cs | 65 +++++++++++++----- .../Controllers/AddSeriesController.cs | 6 +- NzbDrone.Web/NzbDrone.Web.csproj | 8 +-- 22 files changed, 210 insertions(+), 121 deletions(-) delete mode 100644 Libraries/XemLib/XemLib.dll delete mode 100644 Libraries/XemLib/XemLib.pdb diff --git a/Libraries/XemLib/XemLib.dll b/Libraries/XemLib/XemLib.dll deleted file mode 100644 index b32ae3128c86729098a1e21c4de42858d6bfd870..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33280 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
  • P{IH@^pw~_x8F7G*`y|mDFfHrFF+|nxB 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 Nq3(ZMK)d#&Gk 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 Y-jaaUmmTk|D)rT-PXc`s$WsFU2U=Q)+W-In diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 91bf04d2c..42e947a8a 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -128,13 +128,14 @@ + + False + ..\Libraries\TvdbLib.dll + False ..\packages\WebActivator.1.5\lib\net40\WebActivator.dll - - ..\Libraries\XemLib\XemLib.dll - diff --git a/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest.cs index d17aa670a..9870924f1 100644 --- a/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest.cs @@ -17,7 +17,7 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common.AutoMoq; using PetaPoco; -using XemLib.Data; +using TvdbLib.Data; namespace NzbDrone.Core.Test.ProviderTests { @@ -161,7 +161,7 @@ public void RefreshEpisodeInfo_emptyRepo() c => c.Episodes = new List(Builder.CreateListOfSize(episodeCount). All() - .With(l => l.Language = "en") + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) .Build()) ).With(c => c.Id = seriesId).Build(); @@ -172,7 +172,7 @@ public void RefreshEpisodeInfo_emptyRepo() Db.Insert(fakeSeries); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(fakeEpisodes); //Act @@ -195,7 +195,7 @@ public void RefreshEpisodeInfo_should_set_older_than_1900_to_null() c => c.Episodes = new List(Builder.CreateListOfSize(10). All() - .With(l => l.Language = "en").And(e => e.FirstAired = DateTime.Now) + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")).And(e => e.FirstAired = DateTime.Now) .TheFirst(7).With(e => e.FirstAired = new DateTime(1800, 1, 1)) .Build()) ).With(c => c.Id = seriesId).Build(); @@ -207,7 +207,7 @@ public void RefreshEpisodeInfo_should_set_older_than_1900_to_null() Db.Insert(fakeSeries); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(fakeEpisodes); @@ -236,7 +236,7 @@ public void RefreshEpisodeInfo_should_set_older_than_1900_to_null_for_existing_e c => c.Episodes = new List(Builder.CreateListOfSize(1) .All() - .With(l => l.Language = "en").And(e => e.FirstAired = DateTime.Now) + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")).And(e => e.FirstAired = DateTime.Now) .TheFirst(1).With(e => e.FirstAired = new DateTime(1800, 1, 1)) .Build()) ).With(c => c.Id = seriesId).Build(); @@ -248,7 +248,7 @@ public void RefreshEpisodeInfo_should_set_older_than_1900_to_null_for_existing_e Db.Insert(fakeEpisode); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(fakeTvDbEpisodes); //Act @@ -271,7 +271,7 @@ public void RefreshEpisodeInfo_ignore_episode_zero() c => c.Episodes = new List(Builder.CreateListOfSize(episodeCount). All() - .With(l => l.Language = "en") + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) .TheFirst(1) .With(e => e.EpisodeNumber = 0) .With(e => e.SeasonNumber = 15) @@ -285,7 +285,7 @@ public void RefreshEpisodeInfo_ignore_episode_zero() Db.Insert(fakeSeries); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(fakeEpisodes); @@ -324,7 +324,7 @@ public void RefreshEpisodeInfo_should_skip_future_episodes_with_no_title() Db.Insert(fakeSeries); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(fakeEpisodes); @@ -362,7 +362,7 @@ public void RefreshEpisodeInfo_should_skip_older_than_1900_year_episodes_with_no Db.Insert(fakeSeries); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(fakeEpisodes); @@ -395,7 +395,7 @@ public void RefreshEpisodeInfo_should_add_future_episodes_with_title() Db.Insert(fakeSeries); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(fakeEpisodes); @@ -428,7 +428,7 @@ public void RefreshEpisodeInfo_should_add_old_episodes_with_no_title() Db.Insert(fakeSeries); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(fakeEpisodes); @@ -451,7 +451,7 @@ public void RefreshEpisodeInfo_ignore_season_zero() c => c.Episodes = new List(Builder.CreateListOfSize(episodeCount). All() - .With(l => l.Language = "en") + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) .With(e => e.SeasonNumber = 0) .Build()) ).With(c => c.Id = seriesId).Build(); @@ -463,7 +463,7 @@ public void RefreshEpisodeInfo_ignore_season_zero() Db.Insert(fakeSeries); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(fakeEpisodes); Mocker.GetMock() @@ -493,7 +493,7 @@ public void new_episodes_only_calls_Insert() var currentEpisodes = new List(); Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(tvdbSeries); Mocker.GetMock() @@ -528,7 +528,7 @@ public void existing_episodes_only_calls_Update() } Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(tvdbSeries); Mocker.GetMock() @@ -565,7 +565,7 @@ public void should_try_to_get_existing_episode_using_tvdbid_first() .Returns(fakeEpisodeList); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(fakeTvDbResult); //Act @@ -602,7 +602,7 @@ public void should_try_to_get_existing_episode_using_tvdbid_first_then_season_ep var fakeSeries = Builder.CreateNew().With(c => c.SeriesId = seriesId).Build(); Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(tvdbSeries); Mocker.GetMock() @@ -643,7 +643,7 @@ public void existing_episodes_keep_their_episodeId_file_id() } Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(tvdbSeries); var updatedEpisodes = new List(); @@ -692,7 +692,7 @@ public void existing_episodes_remote_their_episodeId_file_id_when_episode_number } Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(tvdbSeries); var updatedEpisodes = new List(); @@ -738,7 +738,7 @@ public void existing_episodes_remote_their_episodeId_file_id_when_season_number_ } Mocker.GetMock(MockBehavior.Strict) - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(tvdbSeries); var updatedEpisodes = new List(); @@ -777,7 +777,7 @@ public void RefreshEpisodeInfo_should_ignore_new_episode_for_ignored_season() c => c.Episodes = new List(Builder.CreateListOfSize(episodeCount). All() - .With(l => l.Language = "en") + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) .With(e => e.SeasonNumber = 5) .TheFirst(1) .With(e => e.EpisodeNumber = 1) @@ -796,7 +796,7 @@ public void RefreshEpisodeInfo_should_ignore_new_episode_for_ignored_season() Db.Insert(fakeEpisode); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(tvdbSeries); Mocker.GetMock() @@ -1486,7 +1486,7 @@ public void RefreshEpisodeInfo_should_ignore_episode_zero_except_if_season_one() c => c.Episodes = new List(Builder.CreateListOfSize(episodeCount). All() - .With(l => l.Language = "en") + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) .With(e => e.EpisodeNumber = 0) .TheFirst(1) .With(e => e.SeasonNumber = 1) @@ -1508,7 +1508,7 @@ public void RefreshEpisodeInfo_should_ignore_episode_zero_except_if_season_one() Db.Insert(fakeSeries); Mocker.GetMock() - .Setup(c => c.GetSeries(seriesId, true, false, false)) + .Setup(c => c.GetSeries(seriesId, true, false)) .Returns(tvdbSeries); //Act diff --git a/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest_DeleteInvalidEpisodes.cs b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest_DeleteInvalidEpisodes.cs index 2d43a5c03..c4f767a92 100644 --- a/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest_DeleteInvalidEpisodes.cs +++ b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest_DeleteInvalidEpisodes.cs @@ -10,7 +10,7 @@ using NzbDrone.Core.Repository; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common.AutoMoq; -using XemLib.Data; +using TvdbLib.Data; namespace NzbDrone.Core.Test.ProviderTests { @@ -27,7 +27,7 @@ public void Delete_None_Valid_TvDbEpisodeId() var tvDbSeries = Builder.CreateListOfSize(episodeCount). All() - .With(l => l.Language = "en") + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) .Build(); @@ -65,7 +65,7 @@ public void Delete_None_TvDbEpisodeId_is_zero() var tvDbSeries = Builder.CreateListOfSize(episodeCount). All() - .With(l => l.Language = "en") + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) .Build(); var fakeSeries = Builder.CreateNew() @@ -102,7 +102,7 @@ public void Delete_None_TvDbEpisodeId_is_null() var tvDbSeries = Builder.CreateListOfSize(episodeCount). All() - .With(l => l.Language = "en") + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) .Build(); var fakeSeries = Builder.CreateNew() @@ -139,7 +139,7 @@ public void Delete_TvDbId() var tvDbSeries = Builder.CreateListOfSize(episodeCount). All() - .With(l => l.Language = "en") + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) .Build(); var fakeSeries = Builder.CreateNew() @@ -179,7 +179,7 @@ public void Delete_TvDbId_multiple_series() var tvDbSeries = Builder.CreateListOfSize(episodeCount). All() - .With(l => l.Language = "en") + .With(l => l.Language = new TvdbLanguage(0, "eng", "a")) .Build(); var fakeSeries = Builder.CreateNew() diff --git a/NzbDrone.Core.Test/ProviderTests/Metadata/Xbmc_ForEpisodeFile_Fixture.cs b/NzbDrone.Core.Test/ProviderTests/Metadata/Xbmc_ForEpisodeFile_Fixture.cs index c4822c146..48b8ed3fc 100644 --- a/NzbDrone.Core.Test/ProviderTests/Metadata/Xbmc_ForEpisodeFile_Fixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/Metadata/Xbmc_ForEpisodeFile_Fixture.cs @@ -17,8 +17,8 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common.AutoMoq; using NzbDrone.Test.Common; -using XemLib.Data; -using XemLib.Data.Banner; +using TvdbLib.Data; +using TvdbLib.Data.Banner; namespace NzbDrone.Core.Test.ProviderTests.Metadata { @@ -52,30 +52,31 @@ public void Setup() .With(e => e.SeriesId = 79488) .With(e => e.SeasonNumber = 1) .With(e => e.Directors = new List{ "Fake Director" }) - .With(e => e.Writers = new List{ "Fake Writer" }) + .With(e => e.Writer = new List{ "Fake Writer" }) .With(e => e.GuestStars = new List { "Guest Star 1", "Guest Star 2", "Guest Star 3", "" }) .Build(); var seasonBanners = Builder .CreateListOfSize(4) .TheFirst(2) - .With(b => b.SeasonNumber = 1) + .With(b => b.Season = 1) .TheLast(2) - .With(b => b.SeasonNumber = 2) + .With(b => b.Season = 2) .TheFirst(1) - .With(b => b.BannerType = TvdbSeasonBanner.Type.Poster) + .With(b => b.BannerType = TvdbSeasonBanner.Type.season) .With(b => b.BannerPath = "seasons/79488-1-1.jpg") .TheNext(2) - .With(b => b.BannerType = TvdbSeasonBanner.Type.Banner) + .With(b => b.BannerType = TvdbSeasonBanner.Type.seasonwide) .With(b => b.BannerPath = "banners/seasons/79488-test.jpg") .TheLast(1) - .With(b => b.BannerType = TvdbSeasonBanner.Type.Poster) + .With(b => b.BannerType = TvdbSeasonBanner.Type.season) .With(b => b.BannerPath = "seasons/79488-2-1.jpg") .Build(); var seriesActors = Builder .CreateListOfSize(5) .All() + .With(a => a.ActorImage = Builder.CreateNew().Build()) .Build(); tvdbSeries = Builder @@ -84,10 +85,9 @@ public void Setup() .With(s => s.SeriesName = "30 Rock") .With(s => s.TvdbActors = seriesActors.ToList()) .With(s => s.Episodes = tvdbEpisodes.ToList()) - .With(s => s.Banners = new TvdbBanners()) .Build(); - tvdbSeries.Banners.SeasonBanners.AddRange(seasonBanners); + tvdbSeries.Banners.AddRange(seasonBanners); } private void WithUseBanners() @@ -128,7 +128,7 @@ private void WithNoDirectors() private void WithNoWriters() { - tvdbSeries.Episodes.ForEach(e => e.Writers = new List()); + tvdbSeries.Episodes.ForEach(e => e.Writer = new List()); } [Test] @@ -159,7 +159,7 @@ public void should_download_thumbnail_when_thumbnail_path_is_not_null() { WithSingleEpisodeFile(); Mocker.Resolve().CreateForEpisodeFile(episodeFile, tvdbSeries); - Mocker.GetMock().Verify(v => v.Download(tvdbSeries.Episodes.First().Banner, episodeFile.Path.Replace("avi", "tbn")), Times.Once()); + Mocker.GetMock().Verify(v => v.Download(tvdbSeries.Episodes.First().BannerPath, episodeFile.Path.Replace("avi", "tbn")), Times.Once()); } [Test] diff --git a/NzbDrone.Core.Test/ProviderTests/Metadata/Xbmc_ForSeries_Fixture.cs b/NzbDrone.Core.Test/ProviderTests/Metadata/Xbmc_ForSeries_Fixture.cs index 6151bfc11..1a55c0eb7 100644 --- a/NzbDrone.Core.Test/ProviderTests/Metadata/Xbmc_ForSeries_Fixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/Metadata/Xbmc_ForSeries_Fixture.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; @@ -17,8 +16,8 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common.AutoMoq; using NzbDrone.Test.Common; -using XemLib.Data; -using XemLib.Data.Banner; +using TvdbLib.Data; +using TvdbLib.Data.Banner; namespace NzbDrone.Core.Test.ProviderTests.Metadata { @@ -43,23 +42,24 @@ public void Setup() var seasonBanners = Builder .CreateListOfSize(4) .TheFirst(2) - .With(b => b.SeasonNumber = 1) + .With(b => b.Season = 1) .TheLast(2) - .With(b => b.SeasonNumber = 2) + .With(b => b.Season = 2) .TheFirst(1) - .With(b => b.BannerType = TvdbSeasonBanner.Type.Poster) + .With(b => b.BannerType = TvdbSeasonBanner.Type.season) .With(b => b.BannerPath = "seasons/79488-1-1.jpg") .TheNext(2) - .With(b => b.BannerType = TvdbSeasonBanner.Type.Banner) + .With(b => b.BannerType = TvdbSeasonBanner.Type.seasonwide) .With(b => b.BannerPath = "banners/seasons/79488-test.jpg") .TheLast(1) - .With(b => b.BannerType = TvdbSeasonBanner.Type.Poster) + .With(b => b.BannerType = TvdbSeasonBanner.Type.season) .With(b => b.BannerPath = "seasons/79488-2-1.jpg") .Build(); var seriesActors = Builder .CreateListOfSize(5) .All() + .With(a => a.ActorImage = Builder.CreateNew().Build()) .Build(); tvdbSeries = Builder @@ -67,10 +67,9 @@ public void Setup() .With(s => s.Id = 79488) .With(s => s.SeriesName = "30 Rock") .With(s => s.TvdbActors = seriesActors.ToList()) - .With(s => s.Banners = new TvdbBanners()) .Build(); - tvdbSeries.Banners.SeasonBanners.AddRange(seasonBanners); + tvdbSeries.Banners.AddRange(seasonBanners); } private void WithUseBanners() @@ -83,18 +82,19 @@ private void WithSpecials() var seasonBanners = Builder .CreateListOfSize(2) .All() - .With(b => b.SeasonNumber = 0) + .With(b => b.Season = 0) .TheFirst(1) - .With(b => b.BannerType = TvdbSeasonBanner.Type.Poster) + .With(b => b.BannerType = TvdbSeasonBanner.Type.season) .With(b => b.BannerPath = "seasons/79488-0-1.jpg") .TheLast(1) - .With(b => b.BannerType = TvdbSeasonBanner.Type.Banner) + .With(b => b.BannerType = TvdbSeasonBanner.Type.seasonwide) .With(b => b.BannerPath = "banners/seasons/79488-0-1.jpg") .Build(); var seriesActors = Builder .CreateListOfSize(5) .All() + .With(a => a.ActorImage = Builder.CreateNew().Build()) .Build(); tvdbSeries = Builder @@ -102,11 +102,9 @@ private void WithSpecials() .With(s => s.Id = 79488) .With(s => s.SeriesName = "30 Rock") .With(s => s.TvdbActors = seriesActors.ToList()) - .With(s => s.Banners = new TvdbBanners()) - .With(s => s.Genres = new List { "Comedy" }) .Build(); - tvdbSeries.Banners.SeasonBanners.AddRange(seasonBanners); + tvdbSeries.Banners.AddRange(seasonBanners); } [Test] @@ -126,14 +124,14 @@ public void should_call_diskprovider_writeAllText() public void should_download_fanart() { Mocker.Resolve().CreateForSeries(series, tvdbSeries); - Mocker.GetMock().Verify(v => v.Download(tvdbSeries.Fanart, Path.Combine(series.Path, "fanart.jpg")), Times.Once()); + Mocker.GetMock().Verify(v => v.Download(tvdbSeries.FanartPath, Path.Combine(series.Path, "fanart.jpg")), Times.Once()); } [Test] public void should_download_poster_when_useBanners_is_false() { Mocker.Resolve().CreateForSeries(series, tvdbSeries); - Mocker.GetMock().Verify(v => v.Download(tvdbSeries.Poster, Path.Combine(series.Path, "folder.jpg")), Times.Once()); + Mocker.GetMock().Verify(v => v.Download(tvdbSeries.PosterPath, Path.Combine(series.Path, "folder.jpg")), Times.Once()); } [Test] @@ -141,7 +139,7 @@ public void should_download_banner_when_useBanners_is_true() { WithUseBanners(); Mocker.Resolve().CreateForSeries(series, tvdbSeries); - Mocker.GetMock().Verify(v => v.Download(tvdbSeries.Banner, Path.Combine(series.Path, "folder.jpg")), Times.Once()); + Mocker.GetMock().Verify(v => v.Download(tvdbSeries.BannerPath, Path.Combine(series.Path, "folder.jpg")), Times.Once()); } [Test] diff --git a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs index 26e367da3..dd4255406 100644 --- a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/CleanupFixture.cs @@ -18,6 +18,7 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common.AutoMoq; using PetaPoco; +using TvdbLib.Data; namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests { diff --git a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteDirectoryFixture.cs b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteDirectoryFixture.cs index 162baf1e8..e4cd1fd3b 100644 --- a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteDirectoryFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteDirectoryFixture.cs @@ -18,6 +18,7 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common.AutoMoq; using PetaPoco; +using TvdbLib.Data; namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests { diff --git a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteFileFixture.cs b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteFileFixture.cs index 3f4310402..5ac5c3378 100644 --- a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteFileFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/DeleteFileFixture.cs @@ -18,6 +18,7 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common.AutoMoq; using PetaPoco; +using TvdbLib.Data; namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests { diff --git a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/EmptyFixture.cs b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/EmptyFixture.cs index 4b7853392..1835dda2a 100644 --- a/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/EmptyFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/RecycleBinProviderTests/EmptyFixture.cs @@ -18,6 +18,7 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common.AutoMoq; using PetaPoco; +using TvdbLib.Data; namespace NzbDrone.Core.Test.ProviderTests.RecycleBinProviderTests { diff --git a/NzbDrone.Core.Test/ProviderTests/SeasonProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/SeasonProviderTest.cs index 248fb82dc..fa400ab90 100644 --- a/NzbDrone.Core.Test/ProviderTests/SeasonProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/SeasonProviderTest.cs @@ -16,6 +16,7 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common.AutoMoq; using PetaPoco; +using TvdbLib.Data; namespace NzbDrone.Core.Test.ProviderTests { diff --git a/NzbDrone.Core.Test/ProviderTests/TvDbProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/TvDbProviderTest.cs index 42232678e..2dd003ace 100644 --- a/NzbDrone.Core.Test/ProviderTests/TvDbProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/TvDbProviderTest.cs @@ -10,8 +10,8 @@ using NzbDrone.Core.Providers; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; -using XemLib.Data; -using XemLib.Exceptions; +using TvdbLib.Data; +using TvdbLib.Exceptions; namespace NzbDrone.Core.Test.ProviderTests { @@ -30,7 +30,7 @@ public void Setup() [TearDown] public void TearDown() { - ExceptionVerification.MarkInconclusive(typeof(TheTvbdbUnavailableException)); + ExceptionVerification.MarkInconclusive(typeof(TvdbNotAvailableException)); } [TestCase("The Simpsons")] @@ -68,5 +68,58 @@ public void none_unique_season_episode_number() .Max(e => e.Count()).Should().Be(1); } + + [Test] + public void American_dad_fix() + { + //act + var result = tvDbProvider.GetSeries(73141, true); + + var seasonsNumbers = result.Episodes.Select(e => e.SeasonNumber) + .Distinct().ToList(); + + var seasons = new Dictionary>(seasonsNumbers.Count); + + foreach (var season in seasonsNumbers) + { + seasons.Add(season, result.Episodes.Where(e => e.SeasonNumber == season).ToList()); + } + + foreach (var episode in result.Episodes) + { + Console.WriteLine(episode); + } + + //assert + seasonsNumbers.Should().HaveCount(9); + seasons[1].Should().HaveCount(23); + seasons[2].Should().HaveCount(19); + seasons[3].Should().HaveCount(16); + seasons[4].Should().HaveCount(20); + seasons[5].Should().HaveCount(18); + seasons[6].Should().HaveCount(19); + seasons[7].Should().HaveCount(18); + + foreach (var season in seasons) + { + season.Value.Should().OnlyHaveUniqueItems("Season {0}", season.Key); + } + + //Make sure no episode number is skipped + foreach (var season in seasons) + { + for (int i = 1; i < season.Value.Count; i++) + { + //Skip specials, because someone decided 1,3,4,6,7,21 is how you count... + if (season.Key == 0) + continue; + + season.Value.Should().Contain(c => c.EpisodeNumber == i, "Can't find Episode S{0:00}E{1:00}", + season.Value[0].SeasonNumber, i); + } + } + + + } } } \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index ec13af4a1..27f6da914 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -206,6 +206,10 @@ + + False + ..\Libraries\TvdbLib.dll + ..\packages\twitterizer.2.4.0.26532\lib\net40\Twitterizer2.dll @@ -213,9 +217,6 @@ False ..\packages\WebActivator.1.5\lib\net40\WebActivator.dll - - ..\Libraries\XemLib\XemLib.dll - diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index b8221b152..e3e4bfb3e 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -7,7 +7,7 @@ using NzbDrone.Core.Model; using NzbDrone.Core.Repository; using PetaPoco; -using XemLib.Data; +using TvdbLib.Data; namespace NzbDrone.Core.Providers { diff --git a/NzbDrone.Core/Providers/Metadata/MetadataBase.cs b/NzbDrone.Core/Providers/Metadata/MetadataBase.cs index ee9de7e06..740785acc 100644 --- a/NzbDrone.Core/Providers/Metadata/MetadataBase.cs +++ b/NzbDrone.Core/Providers/Metadata/MetadataBase.cs @@ -4,7 +4,7 @@ using NzbDrone.Core.Model; using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Repository; -using XemLib.Data; +using TvdbLib.Data; namespace NzbDrone.Core.Providers.Metadata { diff --git a/NzbDrone.Core/Providers/Metadata/Xbmc.cs b/NzbDrone.Core/Providers/Metadata/Xbmc.cs index 44f9c9e5e..8182cc235 100644 --- a/NzbDrone.Core/Providers/Metadata/Xbmc.cs +++ b/NzbDrone.Core/Providers/Metadata/Xbmc.cs @@ -8,8 +8,8 @@ using NzbDrone.Core.Model; using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Repository; -using XemLib.Data; -using XemLib.Data.Banner; +using TvdbLib.Data; +using TvdbLib.Data.Banner; namespace NzbDrone.Core.Providers.Metadata { @@ -48,16 +48,16 @@ public override void CreateForSeries(Series series, TvdbSeries tvDbSeries) tvShow.Add(new XElement("episodeguideurl", episodeGuideUrl)); tvShow.Add(new XElement("mpaa", tvDbSeries.ContentRating)); tvShow.Add(new XElement("id", tvDbSeries.Id)); - tvShow.Add(new XElement("genre", tvDbSeries.Genres.FirstOrDefault())); + tvShow.Add(new XElement("genre", tvDbSeries.GenreString.Trim('|').Split('|')[0])); tvShow.Add(new XElement("premiered", tvDbSeries.FirstAired.ToString("yyyy-MM-dd"))); - tvShow.Add(new XElement("studio", tvDbSeries.Network)); + tvShow.Add(new XElement("studio", tvDbSeries.Network)); foreach(var actor in tvDbSeries.TvdbActors) { tvShow.Add(new XElement("actor", new XElement("name", actor.Name), new XElement("role", actor.Role), - new XElement("thumb", "http://www.thetvdb.com/banners/" + actor.Image) + new XElement("thumb", "http://www.thetvdb.com/banners/" + actor.ActorImage.BannerPath) )); } @@ -71,7 +71,7 @@ public override void CreateForSeries(Series series, TvdbSeries tvDbSeries) if (!_diskProvider.FileExists(Path.Combine(series.Path, "fanart.jpg"))) { _logger.Debug("Downloading fanart for: {0}", series.Title); - _bannerProvider.Download(tvDbSeries.Fanart, Path.Combine(series.Path, "fanart.jpg")); + _bannerProvider.Download(tvDbSeries.FanartPath, Path.Combine(series.Path, "fanart.jpg")); } if (!_diskProvider.FileExists(Path.Combine(series.Path, "folder.jpg"))) @@ -79,19 +79,19 @@ public override void CreateForSeries(Series series, TvdbSeries tvDbSeries) if(_configProvider.MetadataUseBanners) { _logger.Debug("Downloading series banner for: {0}", series.Title); - _bannerProvider.Download(tvDbSeries.Banner, Path.Combine(series.Path, "folder.jpg")); + _bannerProvider.Download(tvDbSeries.BannerPath, Path.Combine(series.Path, "folder.jpg")); _logger.Debug("Downloading Season banners for {0}", series.Title); - DownloadSeasonThumbnails(series, tvDbSeries, TvdbSeasonBanner.Type.Banner); + DownloadSeasonThumbnails(series, tvDbSeries, TvdbSeasonBanner.Type.seasonwide); } else { _logger.Debug("Downloading series thumbnail for: {0}", series.Title); - _bannerProvider.Download(tvDbSeries.Poster, Path.Combine(series.Path, "folder.jpg")); + _bannerProvider.Download(tvDbSeries.PosterPath, Path.Combine(series.Path, "folder.jpg")); _logger.Debug("Downloading Season posters for {0}", series.Title); - DownloadSeasonThumbnails(series, tvDbSeries, TvdbSeasonBanner.Type.Poster); + DownloadSeasonThumbnails(series, tvDbSeries, TvdbSeasonBanner.Type.season); } } } @@ -112,7 +112,7 @@ public override void CreateForEpisodeFile(EpisodeFile episodeFile, TvdbSeries tv e.SeasonNumber == episodeFile.SeasonNumber && e.EpisodeNumber == episodes.First().EpisodeNumber); - if (episodeFileThumbnail == null || String.IsNullOrWhiteSpace(episodeFileThumbnail.Banner)) + if (episodeFileThumbnail == null || String.IsNullOrWhiteSpace(episodeFileThumbnail.BannerPath)) { _logger.Debug("No thumbnail is available for this episode"); return; @@ -121,7 +121,7 @@ public override void CreateForEpisodeFile(EpisodeFile episodeFile, TvdbSeries tv if (!_diskProvider.FileExists(episodeFile.Path.Replace(Path.GetExtension(episodeFile.Path), ".tbn"))) { _logger.Debug("Downloading episode thumbnail for: {0}", episodeFile.EpisodeFileId); - _bannerProvider.Download(episodeFileThumbnail.Banner, + _bannerProvider.Download(episodeFileThumbnail.BannerPath, episodeFile.Path.Replace(Path.GetExtension(episodeFile.Path), ".tbn")); } @@ -165,9 +165,9 @@ public override void CreateForEpisodeFile(EpisodeFile episodeFile, TvdbSeries tv details.Add(new XElement("plot", tvdbEpisode.Overview)); details.Add(new XElement("displayseason")); details.Add(new XElement("displayepisode")); - details.Add(new XElement("thumb", "http://www.thetvdb.com/banners/" + tvdbEpisode.Banner)); + details.Add(new XElement("thumb", "http://www.thetvdb.com/banners/" + tvdbEpisode.BannerPath)); details.Add(new XElement("watched", "false")); - details.Add(new XElement("credits", tvdbEpisode.Writers.FirstOrDefault())); + details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault())); details.Add(new XElement("director", tvdbEpisode.Directors.FirstOrDefault())); details.Add(new XElement("rating", tvdbEpisode.Rating)); @@ -186,7 +186,7 @@ public override void CreateForEpisodeFile(EpisodeFile episodeFile, TvdbSeries tv details.Add(new XElement("actor", new XElement("name", actor.Name), new XElement("role", actor.Role), - new XElement("thumb", "http://www.thetvdb.com/banners/" + actor.Image) + new XElement("thumb", "http://www.thetvdb.com/banners/" + actor.ActorImage.BannerPath) )); } @@ -235,11 +235,11 @@ public override void RemoveForEpisodeFile(EpisodeFile episodeFile) private void DownloadSeasonThumbnails(Series series, TvdbSeries tvDbSeries, TvdbSeasonBanner.Type bannerType) { - var seasons = tvDbSeries.Banners.SeasonBanners.Where(s => s.BannerType == bannerType).Select(s => s.SeasonNumber); + var seasons = tvDbSeries.SeasonBanners.Where(s => s.BannerType == bannerType).Select(s => s.Season); foreach (var season in seasons) { - var banner = tvDbSeries.Banners.SeasonBanners.FirstOrDefault(b => b.BannerType == bannerType && b.SeasonNumber == season); + var banner = tvDbSeries.SeasonBanners.FirstOrDefault(b => b.BannerType == bannerType && b.Season == season); _logger.Debug("Downloading banner for Season: {0} Series: {1}", season, series.Title); if (season == 0) diff --git a/NzbDrone.Core/Providers/MetadataProvider.cs b/NzbDrone.Core/Providers/MetadataProvider.cs index d6d27ac08..caad52dba 100644 --- a/NzbDrone.Core/Providers/MetadataProvider.cs +++ b/NzbDrone.Core/Providers/MetadataProvider.cs @@ -8,7 +8,7 @@ using NzbDrone.Core.Providers.Metadata; using NzbDrone.Core.Repository; using PetaPoco; -using XemLib.Data; +using TvdbLib.Data; namespace NzbDrone.Core.Providers { @@ -92,7 +92,7 @@ public virtual void Initialize(IList metabaseProviders) public virtual void CreateForSeries(Series series) { - var tvDbSeries = _tvDbProvider.GetSeries(series.SeriesId, false, true, true); + var tvDbSeries = _tvDbProvider.GetSeries(series.SeriesId, false, true); CreateForSeries(series, tvDbSeries); } @@ -107,7 +107,7 @@ public virtual void CreateForSeries(Series series, TvdbSeries tvDbSeries) public virtual void CreateForEpisodeFile(EpisodeFile episodeFile) { - var tvDbSeries = _tvDbProvider.GetSeries(episodeFile.SeriesId, true, true, true); + var tvDbSeries = _tvDbProvider.GetSeries(episodeFile.SeriesId, true, true); CreateForEpisodeFile(episodeFile, tvDbSeries); } @@ -130,7 +130,7 @@ public virtual void CreateForEpisodeFiles(List episodeFiles) Logger.Trace("Creating metadata for {0} files.", episodeFiles.Count); - var tvDbSeries = _tvDbProvider.GetSeries(episodeFiles.First().SeriesId, true, true, true); + var tvDbSeries = _tvDbProvider.GetSeries(episodeFiles.First().SeriesId, true, true); foreach(var episodeFile in episodeFiles) { diff --git a/NzbDrone.Core/Providers/SeriesProvider.cs b/NzbDrone.Core/Providers/SeriesProvider.cs index 9c6d61242..ecbded829 100644 --- a/NzbDrone.Core/Providers/SeriesProvider.cs +++ b/NzbDrone.Core/Providers/SeriesProvider.cs @@ -97,11 +97,11 @@ public virtual Series UpdateSeriesInfo(int seriesId) series.AirsDayOfWeek = tvDbSeries.AirsDayOfWeek; series.Overview = tvDbSeries.Overview; series.Status = tvDbSeries.Status; - series.Language = tvDbSeries.Language != null ? tvDbSeries.Language : string.Empty; + series.Language = tvDbSeries.Language != null ? tvDbSeries.Language.Abbriviation : string.Empty; series.CleanTitle = Parser.NormalizeTitle(tvDbSeries.SeriesName); series.LastInfoSync = DateTime.Now; series.Runtime = (int)tvDbSeries.Runtime; - series.BannerUrl = tvDbSeries.Banner; + series.BannerUrl = tvDbSeries.BannerPath; series.Network = tvDbSeries.Network; UpdateSeries(series); diff --git a/NzbDrone.Core/Providers/TvDbProvider.cs b/NzbDrone.Core/Providers/TvDbProvider.cs index aca384bbc..0bbd29b69 100644 --- a/NzbDrone.Core/Providers/TvDbProvider.cs +++ b/NzbDrone.Core/Providers/TvDbProvider.cs @@ -5,8 +5,9 @@ using NLog; using Ninject; using NzbDrone.Common; -using XemLib; -using XemLib.Data; +using TvdbLib; +using TvdbLib.Cache; +using TvdbLib.Data; namespace NzbDrone.Core.Providers { @@ -16,13 +17,13 @@ public class TvDbProvider public const string TVDB_APIKEY = "5D2D188E86E07F4F"; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private readonly XemClient _xemClient; + private readonly TvdbHandler _handler; [Inject] public TvDbProvider(EnvironmentProvider environmentProvider) { _environmentProvider = environmentProvider; - _xemClient = new XemClient(TVDB_APIKEY); + _handler = new TvdbHandler(new XmlCacheProvider(_environmentProvider.GetCacheFolder()), TVDB_APIKEY); } public TvDbProvider() @@ -32,27 +33,57 @@ public TvDbProvider() public virtual IList SearchSeries(string title) { - Logger.Debug("Searching TVDB for '{0}'", title); + lock (_handler) + { + Logger.Debug("Searching TVDB for '{0}'", title); - var result = _xemClient.SearchSeries(title); + var result = _handler.SearchSeries(title); - Logger.Debug("Search for '{0}' returned {1} possible results", title, result.Count); - return result; + Logger.Debug("Search for '{0}' returned {1} possible results", title, result.Count); + return result; + } } - public virtual TvdbSeries GetSeries(int id, bool loadEpisodes, bool loadActors = false, bool loadBanners = false) + public virtual TvdbSeries GetSeries(int id, bool loadEpisodes, bool loadActors = false) { - Logger.Debug("Fetching SeriesId'{0}' from tvdb", id); - var result = _xemClient.GetSeries(id, loadEpisodes, loadActors, true, TvdbLanguage.Default); + lock (_handler) + { + Logger.Debug("Fetching SeriesId'{0}' from tvdb", id); + var result = _handler.GetSeries(id, TvdbLanguage.DefaultLanguage, loadEpisodes, loadActors, true, true); - //Remove duplicated episodes - var episodes = result.Episodes.OrderByDescending(e => e.FirstAired).ThenByDescending(e => e.EpisodeName) - .GroupBy(e => e.SeriesId.ToString("000000") + e.SeasonNumber.ToString("000") + e.EpisodeNumber.ToString("000")) - .Select(e => e.First()); + //Fix American Dad's scene gongshow + if (result != null && result.Id == 73141) + { + result.Episodes = result.Episodes.Where(e => e.SeasonNumber == 0 || e.EpisodeNumber > 0).ToList(); - result.Episodes = episodes.ToList(); + var seasonOneEpisodeCount = result.Episodes.Where(e => e.SeasonNumber == 1).Count(); + var seasonOneId = result.Episodes.Where(e => e.SeasonNumber == 1).First().SeasonId; - return result; + foreach (var episode in result.Episodes) + { + if (episode.SeasonNumber > 1) + { + if (episode.SeasonNumber == 2) + { + episode.EpisodeNumber = episode.EpisodeNumber + seasonOneEpisodeCount; + episode.SeasonId = seasonOneId; + } + + episode.SeasonNumber = episode.SeasonNumber - 1; + } + + } + } + + //Remove duplicated episodes + var episodes = result.Episodes.OrderByDescending(e => e.FirstAired).ThenByDescending(e => e.EpisodeName) + .GroupBy(e => e.SeriesId.ToString("000000") + e.SeasonNumber.ToString("000") + e.EpisodeNumber.ToString("000")) + .Select(e => e.First()); + + result.Episodes = episodes.ToList(); + + return result; + } } } } \ No newline at end of file diff --git a/NzbDrone.Web/Controllers/AddSeriesController.cs b/NzbDrone.Web/Controllers/AddSeriesController.cs index 541dcea23..96adab086 100644 --- a/NzbDrone.Web/Controllers/AddSeriesController.cs +++ b/NzbDrone.Web/Controllers/AddSeriesController.cs @@ -12,7 +12,7 @@ using NzbDrone.Core.Repository; using NzbDrone.Web.Filters; using NzbDrone.Web.Models; -using XemLib.Exceptions; +using TvdbLib.Exceptions; namespace NzbDrone.Web.Controllers { @@ -156,14 +156,14 @@ public JsonResult LookupSeries(string term) DisplayedTitle = r.FirstAired.Year > 1900 && !r.SeriesName.EndsWith("(" + r.FirstAired.Year + ")") ? string.Format("{0} ({1})", r.SeriesName, r.FirstAired.Year) : r.SeriesName, - Banner = r.Banner, + Banner = r.Banner.BannerPath, Url = String.Format("http://www.thetvdb.com/?tab=series&id={0}", r.Id) }).ToList(); return Json(tvDbResults, JsonRequestBehavior.AllowGet); } - catch (TheTvbdbUnavailableException ex) + catch(TvdbNotAvailableException ex) { logger.WarnException("Unable to lookup series on TheTVDB", ex); return JsonNotificationResult.Info("Lookup Failed", "TheTVDB is not available at this time."); diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index 0bb0c2722..56305e711 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -138,14 +138,14 @@ True + + False + ..\Libraries\TvdbLib.dll + False ..\packages\WebActivator.1.5.1\lib\net40\WebActivator.dll - - False - ..\Libraries\XemLib\XemLib.dll - From c9c967fa1d7fba0f7471a691699d2576a1914724 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 16 Oct 2012 22:00:28 -0700 Subject: [PATCH 03/13] Properly using Xem now --- NzbDrone.Core.Test/Files/Xem/Failure.txt | 7 ++ NzbDrone.Core.Test/Files/Xem/Ids.txt | 24 ++++++ NzbDrone.Core.Test/Files/Xem/Mappings.txt | 32 +++++++ NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 11 +++ .../GetSceneTvdbMappingsFixture.cs | 77 +++++++++++++++++ .../GetXemSeriesIdsFixture.cs | 63 ++++++++++++++ .../Datastore/Migrations/Migration20121016.cs | 18 ++++ NzbDrone.Core/Model/Xem/XemResult.cs | 14 +++ .../Model/Xem/XemSceneTvdbMapping.cs | 13 +++ NzbDrone.Core/Model/Xem/XemValues.cs | 14 +++ NzbDrone.Core/NzbDrone.Core.csproj | 4 + NzbDrone.Core/Providers/EpisodeProvider.cs | 7 +- .../Providers/XemCommunicationProvider.cs | 66 ++++++++++++++ NzbDrone.Core/Providers/XemProvider.cs | 86 +++++++++++++++++++ NzbDrone.Core/Repository/Episode.cs | 3 + 15 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 NzbDrone.Core.Test/Files/Xem/Failure.txt create mode 100644 NzbDrone.Core.Test/Files/Xem/Ids.txt create mode 100644 NzbDrone.Core.Test/Files/Xem/Mappings.txt create mode 100644 NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetSceneTvdbMappingsFixture.cs create mode 100644 NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetXemSeriesIdsFixture.cs create mode 100644 NzbDrone.Core/Datastore/Migrations/Migration20121016.cs create mode 100644 NzbDrone.Core/Model/Xem/XemResult.cs create mode 100644 NzbDrone.Core/Model/Xem/XemSceneTvdbMapping.cs create mode 100644 NzbDrone.Core/Model/Xem/XemValues.cs create mode 100644 NzbDrone.Core/Providers/XemCommunicationProvider.cs create mode 100644 NzbDrone.Core/Providers/XemProvider.cs diff --git a/NzbDrone.Core.Test/Files/Xem/Failure.txt b/NzbDrone.Core.Test/Files/Xem/Failure.txt new file mode 100644 index 000000000..63d217c10 --- /dev/null +++ b/NzbDrone.Core.Test/Files/Xem/Failure.txt @@ -0,0 +1,7 @@ +{ + + "result": "failure", + "data": [ ], + "message": "no show with the tvdb_id 79488 found" + +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/Files/Xem/Ids.txt b/NzbDrone.Core.Test/Files/Xem/Ids.txt new file mode 100644 index 000000000..fb005862c --- /dev/null +++ b/NzbDrone.Core.Test/Files/Xem/Ids.txt @@ -0,0 +1,24 @@ +{ + + "result": "success", + "data": { + "220571": [ + "Is This a Zombie? Of the Dead", + "Kore wa Zombie Desuka?", + "Kore wa Zombie Desuka? Of the Dead", + "Kore wa Zombie Desuka Of the Dead", + "Kore wa Zombie Desu ka - Of the Dead", + "Kore wa Zombie Desu ka of the Dead" + ], + "79151": [ + "Fate Stay Night", + "Fate/Zero", + "Fate Zero", + "Fate/Zero (2012)", + "Fate Zero S2", + "Fate Zero" + ] + }, + "message": "" + +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/Files/Xem/Mappings.txt b/NzbDrone.Core.Test/Files/Xem/Mappings.txt new file mode 100644 index 000000000..bc7f223ac --- /dev/null +++ b/NzbDrone.Core.Test/Files/Xem/Mappings.txt @@ -0,0 +1,32 @@ +{ + + "result": "success", + "data": [ + { + "scene": { + "season": 1, + "episode": 1, + "absolute": 1 + }, + "tvdb": { + "season": 1, + "episode": 1, + "absolute": 1 + } + }, + { + "scene": { + "season": 1, + "episode": 2, + "absolute": 2 + }, + "tvdb": { + "season": 1, + "episode": 2, + "absolute": 2 + } + } + ], + "message": "full mapping for 73388 on tvdb. this was a cached version" + +} \ 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 42e947a8a..f8b43b823 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -191,6 +191,8 @@ + + @@ -332,6 +334,15 @@ Designer Always + + Always + + + Always + + + Always + Designer Always diff --git a/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetSceneTvdbMappingsFixture.cs b/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetSceneTvdbMappingsFixture.cs new file mode 100644 index 000000000..f5ffc95ae --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetSceneTvdbMappingsFixture.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.AutoMoq; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.ProviderTests.XemCommunicationProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class GetSceneTvdbMappingsFixture : CoreTest + { + private void WithFailureJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Failure.txt")); + } + + private void WithIdsJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Ids.txt")); + } + + private void WithMappingsJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Mappings.txt")); + } + + [Test] + public void should_throw_when_failure_is_found() + { + WithFailureJson(); + Assert.Throws(() => Mocker.Resolve().GetSceneTvdbMappings(12345)); + } + + [Test] + public void should_get_list_of_mappings() + { + WithMappingsJson(); + Mocker.Resolve().GetSceneTvdbMappings(12345).Should().NotBeEmpty(); + } + + [Test] + public void should_have_two_mappings() + { + WithMappingsJson(); + Mocker.Resolve().GetSceneTvdbMappings(12345).Should().HaveCount(2); + } + + [Test] + public void should_have_expected_results() + { + WithMappingsJson(); + var results = Mocker.Resolve().GetSceneTvdbMappings(12345); + var first = results.First(); + first.Scene.Absolute.Should().Be(1); + first.Scene.Season.Should().Be(1); + first.Scene.Episode.Should().Be(1); + first.Tvdb.Absolute.Should().Be(1); + first.Tvdb.Season.Should().Be(1); + first.Tvdb.Episode.Should().Be(1); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetXemSeriesIdsFixture.cs b/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetXemSeriesIdsFixture.cs new file mode 100644 index 000000000..55c6885de --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/XemCommunicationProviderTests/GetXemSeriesIdsFixture.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common.AutoMoq; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.ProviderTests.XemCommunicationProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class GetXemSeriesIdsFixture : CoreTest + { + private void WithFailureJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Failure.txt")); + } + + private void WithIdsJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Ids.txt")); + } + + private void WithMappingsJson() + { + Mocker.GetMock().Setup(s => s.DownloadString(It.IsAny())) + .Returns(File.ReadAllText(@".\Files\Xem\Mappings.txt")); + } + + [Test] + public void should_throw_when_failure_is_found() + { + WithFailureJson(); + Assert.Throws(() => Mocker.Resolve().GetXemSeriesIds()); + } + + [Test] + public void should_get_list_of_int() + { + WithIdsJson(); + Mocker.Resolve().GetXemSeriesIds().Should().NotBeEmpty(); + } + + [Test] + public void should_have_two_ids() + { + WithIdsJson(); + Mocker.Resolve().GetXemSeriesIds().Should().HaveCount(2); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/Migrations/Migration20121016.cs b/NzbDrone.Core/Datastore/Migrations/Migration20121016.cs new file mode 100644 index 000000000..22aec34fc --- /dev/null +++ b/NzbDrone.Core/Datastore/Migrations/Migration20121016.cs @@ -0,0 +1,18 @@ +using System; +using System.Data; +using Migrator.Framework; +using NzbDrone.Common; + +namespace NzbDrone.Core.Datastore.Migrations +{ + [Migration(20121016)] + public class Migration20121016 : NzbDroneMigration + { + protected override void MainDbUpgrade() + { + Database.AddColumn("Episodes", new Column("SceneAbsoluteEpisodeNumber", DbType.Int32, ColumnProperty.Null)); + Database.AddColumn("Episodes", new Column("SceneSeasonNumber", DbType.Int32, ColumnProperty.Null)); + Database.AddColumn("Episodes", new Column("SceneEpisodeNumber", DbType.Int32, ColumnProperty.Null)); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Model/Xem/XemResult.cs b/NzbDrone.Core/Model/Xem/XemResult.cs new file mode 100644 index 000000000..408aa6551 --- /dev/null +++ b/NzbDrone.Core/Model/Xem/XemResult.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Model.Xem +{ + public class XemResult + { + public string Result { get; set; } + public T Data { get; set; } + public string Message { get; set; } + } +} diff --git a/NzbDrone.Core/Model/Xem/XemSceneTvdbMapping.cs b/NzbDrone.Core/Model/Xem/XemSceneTvdbMapping.cs new file mode 100644 index 000000000..29e242e5b --- /dev/null +++ b/NzbDrone.Core/Model/Xem/XemSceneTvdbMapping.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Model.Xem +{ + public class XemSceneTvdbMapping + { + public XemValues Scene { get; set; } + public XemValues Tvdb { get; set; } + } +} diff --git a/NzbDrone.Core/Model/Xem/XemValues.cs b/NzbDrone.Core/Model/Xem/XemValues.cs new file mode 100644 index 000000000..781fbd5c4 --- /dev/null +++ b/NzbDrone.Core/Model/Xem/XemValues.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Model.Xem +{ + public class XemValues + { + public int Season { get; set; } + public int Episode { get; set; } + public int Absolute { get; set; } + } +} diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 27f6da914..fd55c83f4 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -292,6 +292,9 @@ + + + @@ -330,6 +333,7 @@ + diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index e3e4bfb3e..398938c5e 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -331,7 +331,7 @@ public virtual void RefreshEpisodeInfo(Series series) episodeToUpdate.TvDbEpisodeId = episode.Id; episodeToUpdate.EpisodeNumber = episode.EpisodeNumber; episodeToUpdate.SeasonNumber = episode.SeasonNumber; - episodeToUpdate.AbsoluteEpisodeNumber = episode.AbsoluteEpisodeNumber; + episodeToUpdate.AbsoluteEpisodeNumber = episode.AbsoluteNumber; episodeToUpdate.Title = episode.EpisodeName; episodeToUpdate.Overview = episode.Overview.Truncate(3500); @@ -435,5 +435,10 @@ public virtual void SetPostDownloadStatus(List episodeIds, PostDownloadStat logger.Trace("Updating PostDownloadStatus for all episodeIds in {0}", episodeIdString); _database.Execute(episodeIdQuery); } + + public virtual void UpdateEpisodes(List episodes) + { + _database.UpdateMany(episodes); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/XemCommunicationProvider.cs b/NzbDrone.Core/Providers/XemCommunicationProvider.cs new file mode 100644 index 000000000..7c0d97e3a --- /dev/null +++ b/NzbDrone.Core/Providers/XemCommunicationProvider.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NLog; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Ninject; +using NzbDrone.Common; +using NzbDrone.Core.Model.Xem; + +namespace NzbDrone.Core.Providers +{ + public class XemCommunicationProvider + { + private readonly HttpProvider _httpProvider; + + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + + private const string XEM_BASE_URL = "http://thexem.de/map/"; + + [Inject] + public XemCommunicationProvider(HttpProvider httpProvider) + { + _httpProvider = httpProvider; + } + + public XemCommunicationProvider() + { + } + + public virtual List GetXemSeriesIds(string origin = "tvdb") + { + _logger.Trace("Fetching Series IDs from: {0}", origin); + var url = String.Format("{0}allNames?origin={1}", XEM_BASE_URL, origin); + var response =_httpProvider.DownloadString(url); + + CheckForFailureResult(response); + + var result = JsonConvert.DeserializeObject>>(JObject.Parse(response).SelectToken("data").ToString()); + + return result.Keys.ToList(); + } + + public virtual List GetSceneTvdbMappings(int id) + { + _logger.Trace("Fetching Mappings for: {0}", id); + var url = String.Format("{0}all?id={1}&origin=tvdb", XEM_BASE_URL, id); + var response = _httpProvider.DownloadString(url); + + CheckForFailureResult(response); + + var result = JsonConvert.DeserializeObject>(JObject.Parse(response).SelectToken("data").ToString()); + + return result; + } + + public virtual void CheckForFailureResult(string response) + { + var result = JsonConvert.DeserializeObject>(response); + + if (result != null && result.Result.Equals("failure", StringComparison.InvariantCultureIgnoreCase)) + throw new Exception("Error response received from Xem: " + result.Message); + } + } +} diff --git a/NzbDrone.Core/Providers/XemProvider.cs b/NzbDrone.Core/Providers/XemProvider.cs new file mode 100644 index 000000000..83381fb0a --- /dev/null +++ b/NzbDrone.Core/Providers/XemProvider.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NLog; +using Ninject; +using NzbDrone.Core.Repository; + +namespace NzbDrone.Core.Providers +{ + public class XemProvider + { + private readonly SeriesProvider _seriesProvider; + private readonly EpisodeProvider _episodeProvider; + private readonly XemCommunicationProvider _xemCommunicationProvider; + + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + + [Inject] + public XemProvider(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, + XemCommunicationProvider xemCommunicationProvider) + { + _seriesProvider = seriesProvider; + _episodeProvider = episodeProvider; + _xemCommunicationProvider = xemCommunicationProvider; + } + + public XemProvider() + { + } + + public virtual void UpdateMappings() + { + _logger.Trace("Starting scene numbering update"); + try + { + var ids = _xemCommunicationProvider.GetXemSeriesIds(); + var series = _seriesProvider.GetAllSeries(); + var wantedSeries = series.Where(s => ids.Contains(s.SeriesId)); + + foreach(var ser in wantedSeries) + { + _logger.Trace("Updating scene numbering mapping for: {0}", ser.Title); + try + { + var episodesToUpdate = new List(); + var mappings = _xemCommunicationProvider.GetSceneTvdbMappings(ser.SeriesId); + + if (mappings == null) + { + _logger.Trace("Mappings for: {0} are null, skipping", ser.Title); + continue; + } + + foreach(var mapping in mappings) + { + _logger.Trace("Setting scene numbering mappings for {0} S{1:00}E{2:00}", ser.Title, mapping.Tvdb.Season, mapping.Tvdb.Episode); + + var episode = _episodeProvider.GetEpisode(ser.SeriesId, mapping.Tvdb.Season, mapping.Tvdb.Episode); + episode.AbsoluteEpisodeNumber = mapping.Scene.Absolute; + episode.SceneSeasonNumber = mapping.Scene.Season; + episode.SceneEpisodeNumber = mapping.Scene.Episode; + episodesToUpdate.Add(episode); + } + + _logger.Trace("Committing scene numbering mappings to database for: {0}", ser.Title); + _episodeProvider.UpdateEpisodes(episodesToUpdate); + } + + catch(Exception ex) + { + _logger.WarnException("Error updating scene numbering mappings for: " + ser, ex); + } + } + + _logger.Trace("Completed scene numbering update"); + } + + catch(Exception ex) + { + _logger.WarnException("Error updating Scene Mappings", ex); + throw; + } + } + } +} diff --git a/NzbDrone.Core/Repository/Episode.cs b/NzbDrone.Core/Repository/Episode.cs index 9c3e38968..f93782fd6 100644 --- a/NzbDrone.Core/Repository/Episode.cs +++ b/NzbDrone.Core/Repository/Episode.cs @@ -22,6 +22,9 @@ public class Episode public Boolean Ignored { get; set; } public PostDownloadStatusType PostDownloadStatus { get; set; } public int AbsoluteEpisodeNumber { get; set; } + public int SceneAbsoluteEpisodeNumber { get; set; } + public int SceneSeasonNumber { get; set; } + public int SceneEpisodeNumber { get; set; } /// /// Gets or sets the grab date. From 9c6d78d4794aa5b691c9d38f77a5f977245ea736 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 17 Oct 2012 00:39:06 -0700 Subject: [PATCH 04/13] Cleanup and updates for XEM SceneSource added to signify to lookup via scene name Use Episodes for naming instead of EpisodeNumbers (in ParseResult) --- NzbDrone.Common.Test/DiskProviderFixture.cs | 12 +++++ NzbDrone.Common/DiskProvider.cs | 8 ++++ .../NzbDrone.Core.Test.ncrunchproject | 3 ++ .../CustomStartDateSpecificationFixture.cs | 14 +++--- .../UpgradeDiskSpecificationFixtrue.cs | 22 +++++---- .../UpgradeHistorySpecificationFixtrue.cs | 19 ++++---- .../ImportFileFixture.cs | 45 +++++++++++++++++++ .../ProviderTests/DownloadProviderFixture.cs | 9 +++- .../Datastore/Migrations/Migration20121016.cs | 2 + NzbDrone.Core/Model/EpisodeParseResult.cs | 4 ++ NzbDrone.Core/NzbDrone.Core.csproj | 2 + .../CustomStartDateSpecification.cs | 4 +- .../MonitoredEpisodeSpecification.cs | 1 + .../UpgradeDiskSpecification.cs | 2 +- .../UpgradeHistorySpecification.cs | 2 +- NzbDrone.Core/Providers/DiskScanProvider.cs | 4 ++ NzbDrone.Core/Providers/DownloadProvider.cs | 8 ++-- NzbDrone.Core/Providers/EpisodeProvider.cs | 31 +++++++++++-- .../Providers/Indexer/IndexerBase.cs | 1 + .../Providers/SearchHistoryProvider.cs | 2 + NzbDrone.Core/Providers/SearchProvider.cs | 4 ++ NzbDrone.Core/Repository/Series.cs | 2 + 22 files changed, 157 insertions(+), 44 deletions(-) diff --git a/NzbDrone.Common.Test/DiskProviderFixture.cs b/NzbDrone.Common.Test/DiskProviderFixture.cs index a4762bbdd..469cc77ab 100644 --- a/NzbDrone.Common.Test/DiskProviderFixture.cs +++ b/NzbDrone.Common.Test/DiskProviderFixture.cs @@ -175,6 +175,18 @@ public void check_last_write() Console.WriteLine(new DirectoryInfo(@"C:\DRIVERS").LastWriteTimeUtc); } + [Test] + public void IsChildOfPath_should_return_true_when_it_is_a_child() + { + Mocker.Resolve().IsChildOfPath(@"C:\Test\TV", @"C:\Test").Should().BeTrue(); + } + + [Test] + public void IsChildOfPath_should_return_false_when_it_is_not_a_child() + { + Mocker.Resolve().IsChildOfPath(@"C:\NOT_Test\TV", @"C:\Test").Should().BeFalse(); + } + private void VerifyCopy() { BinFolder.Refresh(); diff --git a/NzbDrone.Common/DiskProvider.cs b/NzbDrone.Common/DiskProvider.cs index a56e72b1f..9e1bb334d 100644 --- a/NzbDrone.Common/DiskProvider.cs +++ b/NzbDrone.Common/DiskProvider.cs @@ -235,5 +235,13 @@ public virtual void DirectorySetLastWriteTimeUtc(string path, DateTime dateTime) { Directory.SetLastWriteTimeUtc(path, dateTime); } + + public virtual bool IsChildOfPath(string child, string parent) + { + if (Path.GetFullPath(child).StartsWith(Path.GetFullPath(parent))) + return true; + + return false; + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.ncrunchproject b/NzbDrone.Core.Test/NzbDrone.Core.Test.ncrunchproject index 19f185295..35a316e01 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.ncrunchproject +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.ncrunchproject @@ -23,5 +23,8 @@ NzbDrone.Core.Test.Integeration.ServiceIntegerationFixture.should_be_able_to_submit_exceptions + + NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests.ImportFileFixture.import_unparsable_file_should_skip + \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/CustomStartDateSpecificationFixture.cs b/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/CustomStartDateSpecificationFixture.cs index 6e5b5d0da..f122e2cfa 100644 --- a/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/CustomStartDateSpecificationFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/CustomStartDateSpecificationFixture.cs @@ -32,6 +32,9 @@ public void Setup() { _customStartDateSpecification = Mocker.Resolve(); + firstEpisode = new Episode { AirDate = DateTime.Today }; + secondEpisode = new Episode { AirDate = DateTime.Today }; + fakeSeries = Builder.CreateNew() .With(c => c.Monitored = true) .With(c => c.CustomStartDate = null) @@ -43,6 +46,7 @@ public void Setup() Series = fakeSeries, EpisodeNumbers = new List { 3, 4 }, SeasonNumber = 12, + Episodes = new List { firstEpisode, secondEpisode } }; parseResultSingle = new EpisodeParseResult @@ -51,16 +55,8 @@ public void Setup() Series = fakeSeries, EpisodeNumbers = new List { 3 }, SeasonNumber = 12, + Episodes = new List { firstEpisode } }; - - firstEpisode = new Episode { AirDate = DateTime.Today }; - secondEpisode = new Episode { AirDate = DateTime.Today }; - - var singleEpisodeList = new List { firstEpisode }; - var doubleEpisodeList = new List { firstEpisode, secondEpisode }; - - Mocker.GetMock().Setup(c => c.GetEpisodesByParseResult(parseResultSingle)).Returns(singleEpisodeList); - Mocker.GetMock().Setup(c => c.GetEpisodesByParseResult(parseResultMulti)).Returns(doubleEpisodeList); } private void WithFirstEpisodeLastYear() diff --git a/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/UpgradeDiskSpecificationFixtrue.cs b/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/UpgradeDiskSpecificationFixtrue.cs index bd8a2a39f..ae377f332 100644 --- a/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/UpgradeDiskSpecificationFixtrue.cs +++ b/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/UpgradeDiskSpecificationFixtrue.cs @@ -31,6 +31,12 @@ public void Setup() Mocker.Resolve(); _upgradeDisk = Mocker.Resolve(); + firstFile = new EpisodeFile { Quality = QualityTypes.Bluray1080p, Proper = true }; + secondFile = new EpisodeFile { Quality = QualityTypes.Bluray1080p, Proper = true }; + + var singleEpisodeList = new List { new Episode { EpisodeFile = firstFile }, new Episode { EpisodeFile = null } }; + var doubleEpisodeList = new List { new Episode { EpisodeFile = firstFile }, new Episode { EpisodeFile = secondFile }, new Episode { EpisodeFile = null } }; + var fakeSeries = Builder.CreateNew() .With(c => c.QualityProfile = new QualityProfile { Cutoff = QualityTypes.Bluray1080p }) .Build(); @@ -41,6 +47,7 @@ public void Setup() Quality = new Quality(QualityTypes.DVD, true), EpisodeNumbers = new List { 3, 4 }, SeasonNumber = 12, + Episodes = doubleEpisodeList }; parseResultSingle = new EpisodeParseResult @@ -49,17 +56,8 @@ public void Setup() Quality = new Quality(QualityTypes.DVD, true), EpisodeNumbers = new List { 3 }, SeasonNumber = 12, + Episodes = singleEpisodeList }; - - - firstFile = new EpisodeFile { Quality = QualityTypes.Bluray1080p, Proper = true }; - secondFile = new EpisodeFile { Quality = QualityTypes.Bluray1080p, Proper = true }; - - var singleEpisodeList = new List { new Episode { EpisodeFile = firstFile }, new Episode { EpisodeFile = null } }; - var doubleEpisodeList = new List { new Episode { EpisodeFile = firstFile }, new Episode { EpisodeFile = secondFile }, new Episode { EpisodeFile = null } }; - - Mocker.GetMock().Setup(c => c.GetEpisodesByParseResult(parseResultSingle)).Returns(singleEpisodeList); - Mocker.GetMock().Setup(c => c.GetEpisodesByParseResult(parseResultMulti)).Returns(doubleEpisodeList); } private void WithFirstFileUpgradable() @@ -73,9 +71,9 @@ private void WithSecondFileUpgradable() } [Test] - public void should_return_false_if_single_episode_doesnt_exist_on_disk() + public void should_return_true_if_single_episode_doesnt_exist_on_disk() { - Mocker.GetMock().Setup(c => c.GetEpisodesByParseResult(parseResultSingle)).Returns(new List()); + parseResultSingle.Episodes = new List(); _upgradeDisk.IsSatisfiedBy(parseResultSingle).Should().BeTrue(); } diff --git a/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/UpgradeHistorySpecificationFixtrue.cs b/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/UpgradeHistorySpecificationFixtrue.cs index 85e0ff0fe..ce5fff251 100644 --- a/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/UpgradeHistorySpecificationFixtrue.cs +++ b/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/UpgradeHistorySpecificationFixtrue.cs @@ -31,6 +31,13 @@ public void Setup() Mocker.Resolve(); _upgradeHistory = Mocker.Resolve(); + var singleEpisodeList = new List { new Episode { SeasonNumber = 12, EpisodeNumber = 3 } }; + var doubleEpisodeList = new List { + new Episode { SeasonNumber = 12, EpisodeNumber = 3 }, + new Episode { SeasonNumber = 12, EpisodeNumber = 4 }, + new Episode { SeasonNumber = 12, EpisodeNumber = 5 } + }; + var fakeSeries = Builder.CreateNew() .With(c => c.QualityProfile = new QualityProfile { Cutoff = QualityTypes.Bluray1080p }) .Build(); @@ -41,6 +48,7 @@ public void Setup() Quality = new Quality(QualityTypes.DVD, true), EpisodeNumbers = new List { 3, 4 }, SeasonNumber = 12, + Episodes = doubleEpisodeList }; parseResultSingle = new EpisodeParseResult @@ -49,21 +57,12 @@ public void Setup() Quality = new Quality(QualityTypes.DVD, true), EpisodeNumbers = new List { 3 }, SeasonNumber = 12, + Episodes = singleEpisodeList }; firstQuality = new Quality(QualityTypes.Bluray1080p, true); secondQuality = new Quality(QualityTypes.Bluray1080p, true); - var singleEpisodeList = new List { new Episode { SeasonNumber = 12, EpisodeNumber = 3 } }; - var doubleEpisodeList = new List { - new Episode { SeasonNumber = 12, EpisodeNumber = 3 }, - new Episode { SeasonNumber = 12, EpisodeNumber = 4 }, - new Episode { SeasonNumber = 12, EpisodeNumber = 5 } - }; - - Mocker.GetMock().Setup(c => c.GetEpisodesByParseResult(parseResultSingle)).Returns(singleEpisodeList); - Mocker.GetMock().Setup(c => c.GetEpisodesByParseResult(parseResultMulti)).Returns(doubleEpisodeList); - Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(fakeSeries.SeriesId, 12, 3)).Returns(firstQuality); Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(fakeSeries.SeriesId, 12, 4)).Returns(secondQuality); Mocker.GetMock().Setup(c => c.GetBestQualityInHistory(fakeSeries.SeriesId, 12, 5)).Returns(null); diff --git a/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/ImportFileFixture.cs b/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/ImportFileFixture.cs index 21b9acf69..52670e1c9 100644 --- a/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/ImportFileFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/ImportFileFixture.cs @@ -201,6 +201,9 @@ public void import_file_with_no_episode_in_db_should_skip() Mocker.GetMock(MockBehavior.Strict) .Setup(e => e.GetSize(fileName)).Returns(90000000000); + Mocker.GetMock(MockBehavior.Strict) + .Setup(e => e.IsChildOfPath(fileName, fakeSeries.Path)).Returns(false); + Mocker.GetMock() .Setup(c => c.GetEpisodesByParseResult(It.IsAny())) .Returns(new List()); @@ -416,5 +419,47 @@ private static void VerifySkipImport(EpisodeFile result, AutoMoqer Mocker) Mocker.GetMock().Verify(p => p.UpdateEpisode(It.IsAny()), Times.Never()); Mocker.GetMock().Verify(p => p.DeleteFile(It.IsAny()), Times.Never()); } + + [Test] + public void should_set_parseResult_SceneSource_if_not_in_series_Path() + { + var series = Builder + .CreateNew() + .With(s => s.Path == @"C:\Test\TV\30 Rock") + .Build(); + + const string path = @"C:\Test\Unsorted TV\30 Rock\30.rock.s01e01.pilot.mkv"; + + Mocker.GetMock().Setup(s => s.GetEpisodesByParseResult(It.IsAny())) + .Returns(new List()); + + Mocker.GetMock().Setup(s => s.IsChildOfPath(path, series.Path)) + .Returns(false); + + Mocker.Resolve().ImportFile(series, path); + + Mocker.Verify(s => s.GetEpisodesByParseResult(It.Is(p => p.SceneSource)), Times.Once()); + } + + [Test] + public void should_not_set_parseResult_SceneSource_if_in_series_Path() + { + var series = Builder + .CreateNew() + .With(s => s.Path == @"C:\Test\TV\30 Rock") + .Build(); + + const string path = @"C:\Test\TV\30 Rock\30.rock.s01e01.pilot.mkv"; + + Mocker.GetMock().Setup(s => s.GetEpisodesByParseResult(It.IsAny())) + .Returns(new List()); + + Mocker.GetMock().Setup(s => s.IsChildOfPath(path, series.Path)) + .Returns(true); + + Mocker.Resolve().ImportFile(series, path); + + Mocker.Verify(s => s.GetEpisodesByParseResult(It.Is(p => p.SceneSource == false)), Times.Once()); + } } } diff --git a/NzbDrone.Core.Test/ProviderTests/DownloadProviderFixture.cs b/NzbDrone.Core.Test/ProviderTests/DownloadProviderFixture.cs index 179478611..2886b8074 100644 --- a/NzbDrone.Core.Test/ProviderTests/DownloadProviderFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/DownloadProviderFixture.cs @@ -43,6 +43,7 @@ private EpisodeParseResult SetupParseResult() .With(c => c.Quality = new Quality(QualityTypes.DVD, false)) .With(c => c.Series = Builder.CreateNew().Build()) .With(c => c.EpisodeNumbers = new List{2}) + .With(c => c.Episodes = episodes) .Build(); } @@ -191,6 +192,11 @@ public string create_proper_sab_titles(int seasons, int[] episodes, string title .With(c => c.Title = "My Series Name") .Build(); + var fakeEpisodes = new List(); + + foreach(var episode in episodes) + fakeEpisodes.Add(Builder.CreateNew().With(e => e.EpisodeNumber = episode).Build()); + var parsResult = new EpisodeParseResult() { AirDate = DateTime.Now, @@ -198,7 +204,8 @@ public string create_proper_sab_titles(int seasons, int[] episodes, string title Quality = new Quality(quality, proper), SeasonNumber = seasons, Series = series, - EpisodeTitle = title + EpisodeTitle = title, + Episodes = fakeEpisodes }; return Mocker.Resolve().GetDownloadTitle(parsResult); diff --git a/NzbDrone.Core/Datastore/Migrations/Migration20121016.cs b/NzbDrone.Core/Datastore/Migrations/Migration20121016.cs index 22aec34fc..0a8c55e5b 100644 --- a/NzbDrone.Core/Datastore/Migrations/Migration20121016.cs +++ b/NzbDrone.Core/Datastore/Migrations/Migration20121016.cs @@ -13,6 +13,8 @@ protected override void MainDbUpgrade() Database.AddColumn("Episodes", new Column("SceneAbsoluteEpisodeNumber", DbType.Int32, ColumnProperty.Null)); Database.AddColumn("Episodes", new Column("SceneSeasonNumber", DbType.Int32, ColumnProperty.Null)); Database.AddColumn("Episodes", new Column("SceneEpisodeNumber", DbType.Int32, ColumnProperty.Null)); + + Database.AddColumn("Series", new Column("UseSceneNumbering", DbType.Boolean, ColumnProperty.Null)); } } } \ No newline at end of file diff --git a/NzbDrone.Core/Model/EpisodeParseResult.cs b/NzbDrone.Core/Model/EpisodeParseResult.cs index e2385d5ff..6cf26464a 100644 --- a/NzbDrone.Core/Model/EpisodeParseResult.cs +++ b/NzbDrone.Core/Model/EpisodeParseResult.cs @@ -46,6 +46,10 @@ public string CleanTitle public string ReleaseGroup { get; set; } + public bool SceneSource { get; set; } + + public IList Episodes { get; set; } + public override string ToString() { diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index fd55c83f4..f46817a93 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -227,6 +227,7 @@ + @@ -334,6 +335,7 @@ + diff --git a/NzbDrone.Core/Providers/DecisionEngine/CustomStartDateSpecification.cs b/NzbDrone.Core/Providers/DecisionEngine/CustomStartDateSpecification.cs index 53db72ff1..d96f41a07 100644 --- a/NzbDrone.Core/Providers/DecisionEngine/CustomStartDateSpecification.cs +++ b/NzbDrone.Core/Providers/DecisionEngine/CustomStartDateSpecification.cs @@ -29,9 +29,7 @@ public virtual bool IsSatisfiedBy(EpisodeParseResult subject) return true; } - var episodes = _episodeProvider.GetEpisodesByParseResult(subject); - - if (episodes.Any(episode => episode.AirDate > subject.Series.CustomStartDate.Value)) + if (subject.Episodes.Any(episode => episode.AirDate > subject.Series.CustomStartDate.Value)) { logger.Debug("One or more episodes aired after cutoff, downloading."); return true; diff --git a/NzbDrone.Core/Providers/DecisionEngine/MonitoredEpisodeSpecification.cs b/NzbDrone.Core/Providers/DecisionEngine/MonitoredEpisodeSpecification.cs index 713b556e3..d92e79ae5 100644 --- a/NzbDrone.Core/Providers/DecisionEngine/MonitoredEpisodeSpecification.cs +++ b/NzbDrone.Core/Providers/DecisionEngine/MonitoredEpisodeSpecification.cs @@ -42,6 +42,7 @@ public virtual bool IsSatisfiedBy(EpisodeParseResult subject) } var episodes = _episodeProvider.GetEpisodesByParseResult(subject); + subject.Episodes = episodes; //return monitored if any of the episodes are monitored if (episodes.Any(episode => !episode.Ignored)) diff --git a/NzbDrone.Core/Providers/DecisionEngine/UpgradeDiskSpecification.cs b/NzbDrone.Core/Providers/DecisionEngine/UpgradeDiskSpecification.cs index ee9a500e0..fc37ae424 100644 --- a/NzbDrone.Core/Providers/DecisionEngine/UpgradeDiskSpecification.cs +++ b/NzbDrone.Core/Providers/DecisionEngine/UpgradeDiskSpecification.cs @@ -24,7 +24,7 @@ public UpgradeDiskSpecification() public virtual bool IsSatisfiedBy(EpisodeParseResult subject) { - foreach (var file in _episodeProvider.GetEpisodesByParseResult(subject).Select(c => c.EpisodeFile).Where(c => c != null)) + foreach (var file in subject.Episodes.Select(c => c.EpisodeFile).Where(c => c != null)) { logger.Trace("Comparing file quality with report. Existing file is {0} proper:{1}", file.Quality, file.Proper); if (!_qualityUpgradeSpecification.IsSatisfiedBy(new Quality { QualityType = file.Quality, Proper = file.Proper }, subject.Quality, subject.Series.QualityProfile.Cutoff)) diff --git a/NzbDrone.Core/Providers/DecisionEngine/UpgradeHistorySpecification.cs b/NzbDrone.Core/Providers/DecisionEngine/UpgradeHistorySpecification.cs index 86c61c586..11bcb161b 100644 --- a/NzbDrone.Core/Providers/DecisionEngine/UpgradeHistorySpecification.cs +++ b/NzbDrone.Core/Providers/DecisionEngine/UpgradeHistorySpecification.cs @@ -27,7 +27,7 @@ public UpgradeHistorySpecification() public virtual bool IsSatisfiedBy(EpisodeParseResult subject) { - foreach (var episode in _episodeProvider.GetEpisodesByParseResult(subject)) + foreach (var episode in subject.Episodes) { var bestQualityInHistory = _historyProvider.GetBestQualityInHistory(subject.Series.SeriesId, episode.SeasonNumber, episode.EpisodeNumber); if (bestQualityInHistory != null) diff --git a/NzbDrone.Core/Providers/DiskScanProvider.cs b/NzbDrone.Core/Providers/DiskScanProvider.cs index bf9e2db24..746b5d77b 100644 --- a/NzbDrone.Core/Providers/DiskScanProvider.cs +++ b/NzbDrone.Core/Providers/DiskScanProvider.cs @@ -122,6 +122,9 @@ public virtual EpisodeFile ImportFile(Series series, string filePath) if (parseResult == null) return null; + if (!_diskProvider.IsChildOfPath(filePath, series.Path)) + parseResult.SceneSource = true; + parseResult.SeriesTitle = series.Title; //replaces the nasty path as title to help with logging parseResult.Series = series; @@ -203,6 +206,7 @@ public virtual EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, bool newDown var parseResult = Parser.ParsePath(episodeFile.Path); parseResult.Series = series; parseResult.Quality = new Quality{ QualityType = episodeFile.Quality, Proper = episodeFile.Proper }; + parseResult.Episodes = episodes; var message = _downloadProvider.GetDownloadTitle(parseResult); diff --git a/NzbDrone.Core/Providers/DownloadProvider.cs b/NzbDrone.Core/Providers/DownloadProvider.cs index a4114022b..046047442 100644 --- a/NzbDrone.Core/Providers/DownloadProvider.cs +++ b/NzbDrone.Core/Providers/DownloadProvider.cs @@ -48,13 +48,13 @@ public virtual bool DownloadReport(EpisodeParseResult parseResult) var provider = GetActiveDownloadClient(); - bool success = provider.DownloadNzb(parseResult.NzbUrl, GetDownloadTitle(parseResult)); + bool success = provider.DownloadNzb(parseResult.NzbUrl, downloadTitle); if (success) { logger.Trace("Download added to Queue: {0}", downloadTitle); - foreach (var episode in _episodeProvider.GetEpisodesByParseResult(parseResult)) + foreach (var episode in parseResult.Episodes) { var history = new History { @@ -127,9 +127,9 @@ public virtual String GetDownloadTitle(EpisodeParseResult parseResult) //Show Name - 1x01 - Episode Name var episodeString = new List(); - foreach (var episode in parseResult.EpisodeNumbers) + foreach (var episode in parseResult.Episodes) { - episodeString.Add(String.Format("{0}x{1:00}", parseResult.SeasonNumber, episode)); + episodeString.Add(String.Format("{0}x{1:00}", parseResult.SeasonNumber, episode.EpisodeNumber)); } var epNumberString = String.Join("-", episodeString); diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index 398938c5e..f5514a698 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -188,11 +188,20 @@ public virtual IList GetEpisodesByParseResult(EpisodeParseResult parseR foreach (var episodeNumber in parseResult.EpisodeNumbers) { - var episodeInfo = GetEpisode(parseResult.Series.SeriesId, parseResult.SeasonNumber, episodeNumber); - if (episodeInfo == null && parseResult.AirDate != null) + Episode episodeInfo; + + if (parseResult.SceneSource && parseResult.Series.UseSceneNumbering) + episodeInfo = GetEpisodeFromSceneNumbering(parseResult.Series.SeriesId, parseResult.SeasonNumber, episodeNumber); + + else { - episodeInfo = GetEpisode(parseResult.Series.SeriesId, parseResult.AirDate.Value); + episodeInfo = GetEpisode(parseResult.Series.SeriesId, parseResult.SeasonNumber, episodeNumber); + if (episodeInfo == null && parseResult.AirDate != null) + { + episodeInfo = GetEpisode(parseResult.Series.SeriesId, parseResult.AirDate.Value); + } } + //if still null we should add the temp episode if (episodeInfo == null && autoAddNew) { @@ -440,5 +449,21 @@ public virtual void UpdateEpisodes(List episodes) { _database.UpdateMany(episodes); } + + public virtual Episode GetEpisodeFromSceneNumbering(int seriesId, int seasonNumber, int episodeNumber) + { + var episode = _database.Fetch(@"SELECT * FROM Episodes + INNER JOIN Series ON Episodes.SeriesId = Series.SeriesId + LEFT JOIN EpisodeFiles ON Episodes.EpisodeFileId = EpisodeFiles.EpisodeFileId + WHERE Episodes.SeriesId = @0 AND Episodes.SceneSeasonNumber = @1 AND Episodes.SceneEpisodeNumber = @2", seriesId, seasonNumber, episodeNumber).SingleOrDefault(); + + if (episode == null) + return null; + + if (episode.EpisodeFileId == 0) + episode.EpisodeFile = null; + + return episode; + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Indexer/IndexerBase.cs b/NzbDrone.Core/Providers/Indexer/IndexerBase.cs index 76243629e..11b8c2d91 100644 --- a/NzbDrone.Core/Providers/Indexer/IndexerBase.cs +++ b/NzbDrone.Core/Providers/Indexer/IndexerBase.cs @@ -247,6 +247,7 @@ public EpisodeParseResult ParseFeed(SyndicationItem item) { episodeParseResult.Age = DateTime.Now.Date.Subtract(item.PublishDate.Date).Days; episodeParseResult.OriginalString = title; + episodeParseResult.SceneSource = true; } _logger.Trace("Parsed: {0} from: {1}", episodeParseResult, item.Title.Text); diff --git a/NzbDrone.Core/Providers/SearchHistoryProvider.cs b/NzbDrone.Core/Providers/SearchHistoryProvider.cs index fae40927b..4fb7e97a8 100644 --- a/NzbDrone.Core/Providers/SearchHistoryProvider.cs +++ b/NzbDrone.Core/Providers/SearchHistoryProvider.cs @@ -112,6 +112,8 @@ public virtual void ForceDownload(int itemId) parseResult.NzbUrl = item.NzbUrl; parseResult.Series = series; parseResult.Indexer = item.Indexer; + parseResult.Episodes = _episodeProvider.GetEpisodesByParseResult(parseResult); + parseResult.SceneSource = true; logger.Info("Forcing Download of: {0}", item.ReportTitle); _downloadProvider.DownloadReport(parseResult); diff --git a/NzbDrone.Core/Providers/SearchProvider.cs b/NzbDrone.Core/Providers/SearchProvider.cs index 8aa86a491..727554d59 100644 --- a/NzbDrone.Core/Providers/SearchProvider.cs +++ b/NzbDrone.Core/Providers/SearchProvider.cs @@ -319,6 +319,8 @@ public List ProcessSearchResults(ProgressNotification notific continue; } + episodeParseResult.Episodes = _episodeProvider.GetEpisodesByParseResult(episodeParseResult); + item.SearchError = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult); if (item.SearchError == ReportRejectionType.None) { @@ -403,6 +405,8 @@ public List ProcessSearchResults(ProgressNotification notific continue; } + episodeParseResult.Episodes = _episodeProvider.GetEpisodesByParseResult(episodeParseResult); + item.SearchError = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult); if (item.SearchError == ReportRejectionType.None) { diff --git a/NzbDrone.Core/Repository/Series.cs b/NzbDrone.Core/Repository/Series.cs index 70d19cead..35a66ba92 100644 --- a/NzbDrone.Core/Repository/Series.cs +++ b/NzbDrone.Core/Repository/Series.cs @@ -50,6 +50,8 @@ public class Series public DateTime? CustomStartDate { get; set; } + public bool UseSceneNumbering { get; set; } + /// /// Gets or sets a value indicating whether this is hidden. /// From 59bfa164627b1ae76c7c9ee4bf029b4f6e0829a4 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 17 Oct 2012 00:56:07 -0700 Subject: [PATCH 05/13] Added tests for GetEpisodeBySceneNumbering --- NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 7 +- .../EpisodeProviderTest.cs | 5 +- ...isodeProviderTest_DeleteInvalidEpisodes.cs | 4 +- ...deProviderTest_GetEpisodesByParseResult.cs | 5 +- .../GetEpisodeBySceneNumberFixture.cs | 77 +++++++++++++++++++ NzbDrone.Core/Providers/EpisodeProvider.cs | 4 +- 6 files changed, 86 insertions(+), 16 deletions(-) rename NzbDrone.Core.Test/ProviderTests/{ => EpisodeProviderTests}/EpisodeProviderTest.cs (99%) rename NzbDrone.Core.Test/ProviderTests/{ => EpisodeProviderTests}/EpisodeProviderTest_DeleteInvalidEpisodes.cs (98%) rename NzbDrone.Core.Test/ProviderTests/{ => EpisodeProviderTests}/EpisodeProviderTest_GetEpisodesByParseResult.cs (98%) create mode 100644 NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/GetEpisodeBySceneNumberFixture.cs diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index f8b43b823..c406f37b4 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -153,6 +153,7 @@ + @@ -195,7 +196,7 @@ - + @@ -205,7 +206,7 @@ - + @@ -227,7 +228,7 @@ - + diff --git a/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/EpisodeProviderTest.cs similarity index 99% rename from NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest.cs rename to NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/EpisodeProviderTest.cs index 9870924f1..290026385 100644 --- a/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/EpisodeProviderTest.cs @@ -2,9 +2,7 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; - using FizzWare.NBuilder; using FluentAssertions; using Moq; @@ -15,11 +13,10 @@ using NzbDrone.Core.Repository; using NzbDrone.Core.Repository.Quality; using NzbDrone.Core.Test.Framework; -using NzbDrone.Test.Common.AutoMoq; using PetaPoco; using TvdbLib.Data; -namespace NzbDrone.Core.Test.ProviderTests +namespace NzbDrone.Core.Test.ProviderTests.EpisodeProviderTests { [TestFixture] // ReSharper disable InconsistentNaming diff --git a/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest_DeleteInvalidEpisodes.cs b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/EpisodeProviderTest_DeleteInvalidEpisodes.cs similarity index 98% rename from NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest_DeleteInvalidEpisodes.cs rename to NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/EpisodeProviderTest_DeleteInvalidEpisodes.cs index c4f767a92..70c185136 100644 --- a/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest_DeleteInvalidEpisodes.cs +++ b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/EpisodeProviderTest_DeleteInvalidEpisodes.cs @@ -2,17 +2,15 @@ using System.Collections.Generic; using System.Linq; - using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Providers; using NzbDrone.Core.Repository; using NzbDrone.Core.Test.Framework; -using NzbDrone.Test.Common.AutoMoq; using TvdbLib.Data; -namespace NzbDrone.Core.Test.ProviderTests +namespace NzbDrone.Core.Test.ProviderTests.EpisodeProviderTests { [TestFixture] // ReSharper disable InconsistentNaming diff --git a/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest_GetEpisodesByParseResult.cs b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/EpisodeProviderTest_GetEpisodesByParseResult.cs similarity index 98% rename from NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest_GetEpisodesByParseResult.cs rename to NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/EpisodeProviderTest_GetEpisodesByParseResult.cs index 5f4b8bd4c..2d726a2fa 100644 --- a/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTest_GetEpisodesByParseResult.cs +++ b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/EpisodeProviderTest_GetEpisodesByParseResult.cs @@ -5,17 +5,14 @@ using System.Linq; using FizzWare.NBuilder; using FluentAssertions; -using Moq; using NUnit.Framework; using NzbDrone.Core.Model; using NzbDrone.Core.Providers; using NzbDrone.Core.Repository; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; -using NzbDrone.Test.Common.AutoMoq; -using PetaPoco; -namespace NzbDrone.Core.Test.ProviderTests +namespace NzbDrone.Core.Test.ProviderTests.EpisodeProviderTests { [TestFixture] // ReSharper disable InconsistentNaming diff --git a/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/GetEpisodeBySceneNumberFixture.cs b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/GetEpisodeBySceneNumberFixture.cs new file mode 100644 index 000000000..b4a4726bc --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/EpisodeProviderTests/GetEpisodeBySceneNumberFixture.cs @@ -0,0 +1,77 @@ +// ReSharper disable RedundantUsingDirective + +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.ProviderTests.EpisodeProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class GetEpisodeBySceneNumberFixture : CoreTest + { + private Series _series; + private Episode _episode; + + [SetUp] + public void Setup() + { + WithRealDb(); + + _series = Builder + .CreateNew() + .Build(); + + Db.Insert(_series); + } + + public void WithNullSceneNumbering() + { + _episode = Builder + .CreateNew() + .With(e => e.SeriesId = _series.SeriesId) + .Build(); + + Db.Insert(_episode); + Db.Execute("UPDATE Episodes SET SceneSeasonNumber = NULL, SceneEpisodeNumber = NULL"); + } + + public void WithSceneNumbering() + { + _episode = Builder + .CreateNew() + .With(e => e.SeriesId = _series.SeriesId) + .Build(); + + Db.Insert(_episode); + } + + [Test] + public void should_return_null_if_no_episodes_in_db() + { + Mocker.Resolve().GetEpisodeBySceneNumbering(_series.SeriesId, 1, 1).Should().BeNull(); + } + + [Test] + public void should_return_null_if_no_matching_episode_is_found() + { + WithNullSceneNumbering(); + Mocker.Resolve().GetEpisodeBySceneNumbering(_series.SeriesId, 1, 1).Should().BeNull(); + } + + [Test] + public void should_return_episode_if_matching_episode_is_found() + { + WithSceneNumbering(); + + var result = Mocker.Resolve() + .GetEpisodeBySceneNumbering(_series.SeriesId, _episode.SceneSeasonNumber, _episode.SceneEpisodeNumber); + + result.EpisodeId.Should().Be(_episode.EpisodeId); + } + } +} diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index f5514a698..a675ac0bc 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -191,7 +191,7 @@ public virtual IList GetEpisodesByParseResult(EpisodeParseResult parseR Episode episodeInfo; if (parseResult.SceneSource && parseResult.Series.UseSceneNumbering) - episodeInfo = GetEpisodeFromSceneNumbering(parseResult.Series.SeriesId, parseResult.SeasonNumber, episodeNumber); + episodeInfo = GetEpisodeBySceneNumbering(parseResult.Series.SeriesId, parseResult.SeasonNumber, episodeNumber); else { @@ -450,7 +450,7 @@ public virtual void UpdateEpisodes(List episodes) _database.UpdateMany(episodes); } - public virtual Episode GetEpisodeFromSceneNumbering(int seriesId, int seasonNumber, int episodeNumber) + public virtual Episode GetEpisodeBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber) { var episode = _database.Fetch(@"SELECT * FROM Episodes INNER JOIN Series ON Episodes.SeriesId = Series.SeriesId From 2fd12636195451a98481c0d486b7edee24aeeaae Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 17 Oct 2012 17:06:14 -0700 Subject: [PATCH 06/13] Download naming uses tvdb numbering Searching by scene name added Removed American Dad --- .../ProviderTests/DownloadProviderFixture.cs | 39 +++++++++- .../GetNewFilenameFixture.cs | 38 +++++++++ .../SearchProviderTests/SearchFixture.cs | 26 ++++++- .../ProviderTests/SeriesProviderTest.cs | 36 +++++++++ NzbDrone.Core/Providers/DownloadProvider.cs | 17 +++- NzbDrone.Core/Providers/EpisodeProvider.cs | 10 +++ NzbDrone.Core/Providers/MediaFileProvider.cs | 2 +- NzbDrone.Core/Providers/SearchProvider.cs | 78 ++++++++++++------- NzbDrone.Core/Providers/SeriesProvider.cs | 8 ++ NzbDrone.Core/Providers/TvDbProvider.cs | 24 ------ 10 files changed, 222 insertions(+), 56 deletions(-) diff --git a/NzbDrone.Core.Test/ProviderTests/DownloadProviderFixture.cs b/NzbDrone.Core.Test/ProviderTests/DownloadProviderFixture.cs index 2886b8074..12a07b564 100644 --- a/NzbDrone.Core.Test/ProviderTests/DownloadProviderFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/DownloadProviderFixture.cs @@ -195,7 +195,11 @@ public string create_proper_sab_titles(int seasons, int[] episodes, string title var fakeEpisodes = new List(); foreach(var episode in episodes) - fakeEpisodes.Add(Builder.CreateNew().With(e => e.EpisodeNumber = episode).Build()); + fakeEpisodes.Add(Builder + .CreateNew() + .With(e => e.EpisodeNumber = episode) + .With(e => e.Title = title) + .Build()); var parsResult = new EpisodeParseResult() { @@ -241,16 +245,49 @@ public string create_proper_sab_daily_titles(bool proper) .With(c => c.Title = "My Series Name") .Build(); + var episode = Builder.CreateNew() + .With(e => e.Title = "My Episode Title") + .Build(); + var parsResult = new EpisodeParseResult { AirDate = new DateTime(2011, 12, 1), Quality = new Quality(QualityTypes.Bluray720p, proper), Series = series, EpisodeTitle = "My Episode Title", + Episodes = new List{ episode } }; return Mocker.Resolve().GetDownloadTitle(parsResult); } + [Test] + public void should_not_repeat_the_same_episode_title() + { + var series = Builder.CreateNew() + .With(c => c.Title = "My Series Name") + .Build(); + + var fakeEpisodes = Builder.CreateListOfSize(2) + .All() + .With(e => e.SeasonNumber = 5) + .TheFirst(1) + .With(e => e.Title = "My Episode Title (1)") + .TheLast(1) + .With(e => e.Title = "My Episode Title (2)") + .Build(); + + var parsResult = new EpisodeParseResult + { + AirDate = DateTime.Now, + EpisodeNumbers = new List{ 10, 11 }, + Quality = new Quality(QualityTypes.HDTV, false), + SeasonNumber = 35, + Series = series, + Episodes = fakeEpisodes + }; + + Mocker.Resolve().GetDownloadTitle(parsResult).Should().Be("My Series Name - 5x01-5x02 - My Episode Title [HDTV]"); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/MediaFileProviderTests/GetNewFilenameFixture.cs b/NzbDrone.Core.Test/ProviderTests/MediaFileProviderTests/GetNewFilenameFixture.cs index 86d60ec7c..51cdbb0b7 100644 --- a/NzbDrone.Core.Test/ProviderTests/MediaFileProviderTests/GetNewFilenameFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/MediaFileProviderTests/GetNewFilenameFixture.cs @@ -699,5 +699,43 @@ public void should_have_two_episodeTitles_when_episode_titles_are_not_the_same() //Assert result.Should().Be("30 Rock - S06E06-E07 - Hello + World"); } + + [Test] + public void should_have_two_episodeTitles_when_distinct_count_is_two() + { + //Setup + var fakeConfig = Mocker.GetMock(); + fakeConfig.SetupGet(c => c.SortingIncludeSeriesName).Returns(true); + fakeConfig.SetupGet(c => c.SortingIncludeEpisodeTitle).Returns(true); + fakeConfig.SetupGet(c => c.SortingAppendQuality).Returns(false); + fakeConfig.SetupGet(c => c.SortingSeparatorStyle).Returns(0); + fakeConfig.SetupGet(c => c.SortingNumberStyle).Returns(2); + fakeConfig.SetupGet(c => c.SortingReplaceSpaces).Returns(false); + fakeConfig.SetupGet(c => c.SortingMultiEpisodeStyle).Returns(3); + + var episode = Builder.CreateNew() + .With(e => e.Title = "Hello (3)") + .With(e => e.SeasonNumber = 6) + .With(e => e.EpisodeNumber = 6) + .Build(); + + var episode2 = Builder.CreateNew() + .With(e => e.Title = "Hello (2)") + .With(e => e.SeasonNumber = 6) + .With(e => e.EpisodeNumber = 7) + .Build(); + + var episode3 = Builder.CreateNew() + .With(e => e.Title = "World") + .With(e => e.SeasonNumber = 6) + .With(e => e.EpisodeNumber = 8) + .Build(); + + //Act + string result = Mocker.Resolve().GetNewFilename(new List { episode, episode2, episode3 }, "30 Rock", QualityTypes.HDTV, false, new EpisodeFile()); + + //Assert + result.Should().Be("30 Rock - S06E06-E07-E08 - Hello + World"); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/SearchFixture.cs b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/SearchFixture.cs index 11b119803..8f34f2c9a 100644 --- a/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/SearchFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/SearchFixture.cs @@ -185,7 +185,7 @@ public void PartialSeasonSearch_should_skip_daily_series() } [Test] - public void EpisodeSearch_should_skip_if_air_date_is_null() + public void EpisodeSearch_should_skip_if_air_date_is_null_and_is_a_daily_series() { //Setup _series.IsDaily = true; @@ -206,5 +206,29 @@ public void EpisodeSearch_should_skip_if_air_date_is_null() result.Should().BeFalse(); ExceptionVerification.ExpectedWarns(1); } + + [Test] + public void EpisodeSearch_should_skip_if_sceneNumbering_is_invalid_and_should_use_sceneNumbering() + { + //Setup + _series.UseSceneNumbering = true; + var episode = _episodes.First(); + episode.SceneSeasonNumber = 0; + episode.SceneEpisodeNumber = 0; + episode.Series = _series; + + Mocker.GetMock().Setup(s => s.IsSatisfiedBy(It.IsAny())) + .Returns(true); + + Mocker.GetMock().Setup(s => s.GetEpisode(episode.EpisodeId)) + .Returns(episode); + + //Act + var result = Mocker.Resolve().EpisodeSearch(MockNotification, episode.EpisodeId); + + //Assert + result.Should().BeFalse(); + ExceptionVerification.ExpectedWarns(1); + } } } diff --git a/NzbDrone.Core.Test/ProviderTests/SeriesProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/SeriesProviderTest.cs index 2d765676e..3d66807a9 100644 --- a/NzbDrone.Core.Test/ProviderTests/SeriesProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/SeriesProviderTest.cs @@ -833,5 +833,41 @@ public void delete_series_should_delete_all_rows_related_to_the_series() } + + [Test] + public void Update_UseSceneNumbering_should_update_applicable_series() + { + WithRealDb(); + var series = Builder.CreateListOfSize(5) + .All() + .With(s => s.UseSceneNumbering = false) + .Build(); + + Db.InsertMany(series); + + Mocker.Resolve().UpdateUseSceneNumbering(new []{ 2, 3 }); + + var seriesResults = Db.Fetch(); + seriesResults.Single(s => s.SeriesId == 2).UseSceneNumbering.Should().BeTrue(); + seriesResults.Single(s => s.SeriesId == 3).UseSceneNumbering.Should().BeTrue(); + } + + [Test] + public void Update_UseSceneNumbering_should_not_update_other_series() + { + WithRealDb(); + var series = Builder.CreateListOfSize(5) + .All() + .With(s => s.UseSceneNumbering = false) + .Build(); + + Db.InsertMany(series); + + Mocker.Resolve().UpdateUseSceneNumbering(new [] { 2, 3 }); + + var seriesResults = Db.Fetch(); + seriesResults.Where(s => !s.UseSceneNumbering).Should().HaveCount(3); + seriesResults.Where(s => !s.UseSceneNumbering).Should().NotContain(s => s.SeriesId == 2 || s.SeriesId == 3); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/DownloadProvider.cs b/NzbDrone.Core/Providers/DownloadProvider.cs index 046047442..44a37e2e9 100644 --- a/NzbDrone.Core/Providers/DownloadProvider.cs +++ b/NzbDrone.Core/Providers/DownloadProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Ninject; using NLog; using NzbDrone.Core.Model; @@ -115,7 +116,7 @@ public virtual String GetDownloadTitle(EpisodeParseResult parseResult) if (parseResult.Series.IsDaily) { var dailyResult = String.Format("{0} - {1:yyyy-MM-dd} - {2} [{3}]", seriesTitle, - parseResult.AirDate, parseResult.EpisodeTitle, parseResult.Quality.QualityType); + parseResult.AirDate, parseResult.Episodes.First().Title, parseResult.Quality.QualityType); if (parseResult.Quality.Proper) dailyResult += " [Proper]"; @@ -126,15 +127,25 @@ public virtual String GetDownloadTitle(EpisodeParseResult parseResult) //Show Name - 1x01-1x02 - Episode Name //Show Name - 1x01 - Episode Name var episodeString = new List(); + var episodeNames = new List(); foreach (var episode in parseResult.Episodes) { - episodeString.Add(String.Format("{0}x{1:00}", parseResult.SeasonNumber, episode.EpisodeNumber)); + episodeString.Add(String.Format("{0}x{1:00}", episode.SeasonNumber, episode.EpisodeNumber)); + episodeNames.Add(Parser.CleanupEpisodeTitle(episode.Title)); } var epNumberString = String.Join("-", episodeString); + string episodeName; - var result = String.Format("{0} - {1} - {2} [{3}]", seriesTitle, epNumberString, parseResult.EpisodeTitle, parseResult.Quality.QualityType); + + if (episodeNames.Distinct().Count() == 1) + episodeName = episodeNames.First(); + + else + episodeName = String.Join(" + ", episodeNames.Distinct()); + + var result = String.Format("{0} - {1} - {2} [{3}]", seriesTitle, epNumberString, episodeName, parseResult.Quality.QualityType); if (parseResult.Quality.Proper) { diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index a675ac0bc..c30000176 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -226,6 +226,16 @@ public virtual IList GetEpisodesByParseResult(EpisodeParseResult parseR { result.Add(episodeInfo); + if (parseResult.Series.UseSceneNumbering) + { + logger.Info("Using Scene to TVDB Mapping for: {0} - Scene: {1}x{2:00} - TVDB: {3}x{4:00}", + parseResult.Series.Title, + episodeInfo.SceneSeasonNumber, + episodeInfo.SceneEpisodeNumber, + episodeInfo.SeasonNumber, + episodeInfo.EpisodeNumber); + } + if (parseResult.EpisodeNumbers.Count == 1) { parseResult.EpisodeTitle = episodeInfo.Title.Trim(); diff --git a/NzbDrone.Core/Providers/MediaFileProvider.cs b/NzbDrone.Core/Providers/MediaFileProvider.cs index c0d49e097..fe2bfab4a 100644 --- a/NzbDrone.Core/Providers/MediaFileProvider.cs +++ b/NzbDrone.Core/Providers/MediaFileProvider.cs @@ -208,7 +208,7 @@ public virtual string GetNewFilename(IList episodes, string seriesTitle result += separatorStyle.Pattern + episodeNames.First(); else - result += separatorStyle.Pattern + String.Join(" + ", episodeNames); + result += separatorStyle.Pattern + String.Join(" + ", episodeNames.Distinct()); } if (_configProvider.SortingAppendQuality) diff --git a/NzbDrone.Core/Providers/SearchProvider.cs b/NzbDrone.Core/Providers/SearchProvider.cs index 727554d59..510ee312c 100644 --- a/NzbDrone.Core/Providers/SearchProvider.cs +++ b/NzbDrone.Core/Providers/SearchProvider.cs @@ -25,7 +25,7 @@ public class SearchProvider private readonly AllowedDownloadSpecification _allowedDownloadSpecification; private readonly SearchHistoryProvider _searchHistoryProvider; - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); [Inject] public SearchProvider(EpisodeProvider episodeProvider, DownloadProvider downloadProvider, SeriesProvider seriesProvider, @@ -60,7 +60,7 @@ public virtual List SeasonSearch(ProgressNotification notification, int ser if (series == null) { - Logger.Error("Unable to find an series {0} in database", seriesId); + _logger.Error("Unable to find an series {0} in database", seriesId); return new List(); } @@ -72,17 +72,17 @@ public virtual List SeasonSearch(ProgressNotification notification, int ser var reports = PerformSearch(notification, series, seasonNumber); - Logger.Debug("Finished searching all indexers. Total {0}", reports.Count); + _logger.Debug("Finished searching all indexers. Total {0}", reports.Count); if (reports.Count == 0) return new List(); - Logger.Debug("Getting episodes from database for series: {0} and season: {1}", seriesId, seasonNumber); + _logger.Debug("Getting episodes from database for series: {0} and season: {1}", seriesId, seasonNumber); var episodeNumbers = _episodeProvider.GetEpisodeNumbersBySeason(seriesId, seasonNumber); if (episodeNumbers == null || episodeNumbers.Count == 0) { - Logger.Warn("No episodes in database found for series: {0} and season: {1}.", seriesId, seasonNumber); + _logger.Warn("No episodes in database found for series: {0} and season: {1}.", seriesId, seasonNumber); return new List(); } @@ -111,7 +111,7 @@ public virtual List PartialSeasonSearch(ProgressNotification notification, if (series == null) { - Logger.Error("Unable to find an series {0} in database", seriesId); + _logger.Error("Unable to find an series {0} in database", seriesId); return new List(); } @@ -122,7 +122,7 @@ public virtual List PartialSeasonSearch(ProgressNotification notification, notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber); var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber); var reports = PerformSearch(notification, series, seasonNumber, episodes); - Logger.Debug("Finished searching all indexers. Total {0}", reports.Count); + _logger.Debug("Finished searching all indexers. Total {0}", reports.Count); if (reports.Count == 0) return new List(); @@ -140,14 +140,14 @@ public virtual bool EpisodeSearch(ProgressNotification notification, int episode if (episode == null) { - Logger.Error("Unable to find an episode {0} in database", episodeId); + _logger.Error("Unable to find an episode {0} in database", episodeId); return false; } //Check to see if an upgrade is possible before attempting if (!_upgradePossibleSpecification.IsSatisfiedBy(episode)) { - Logger.Info("Search for {0} was aborted, file in disk meets or exceeds Profile's Cutoff", episode); + _logger.Info("Search for {0} was aborted, file in disk meets or exceeds Profile's Cutoff", episode); notification.CurrentMessage = String.Format("Skipping search for {0}, the file you have is already at cutoff", episode); return false; } @@ -156,11 +156,19 @@ public virtual bool EpisodeSearch(ProgressNotification notification, int episode if (episode.Series.IsDaily && !episode.AirDate.HasValue) { - Logger.Warn("AirDate is not Valid for: {0}", episode); + _logger.Warn("AirDate is not Valid for: {0}", episode); notification.CurrentMessage = String.Format("Search for {0} Failed, AirDate is invalid", episode); return false; } + if (episode.Series.UseSceneNumbering && episode.SceneSeasonNumber <= 0 && episode.SceneEpisodeNumber <= 0) + { + _logger.Warn("Series should use Scene Numbering, but it is not available: {0}", episode); + + notification.CurrentMessage = String.Format("Search Failed, invalid scene episode data found: {0}", episode); + return false; + } + var searchResult = new SearchHistory { SearchTime = DateTime.Now, @@ -169,7 +177,7 @@ public virtual bool EpisodeSearch(ProgressNotification notification, int episode var reports = PerformSearch(notification, episode.Series, episode.SeasonNumber, new List { episode }); - Logger.Debug("Finished searching all indexers. Total {0}", reports.Count); + _logger.Debug("Finished searching all indexers. Total {0}", reports.Count); notification.CurrentMessage = "Processing search results"; if (episode.Series.IsDaily) @@ -181,6 +189,24 @@ public virtual bool EpisodeSearch(ProgressNotification notification, int episode return true; } + else if (episode.Series.UseSceneNumbering) + { + searchResult.EpisodeId = episodeId; + searchResult.SearchHistoryItems = ProcessSearchResults( + notification, + reports, + searchResult, + episode.Series, + episode.SceneSeasonNumber, + episode.SceneEpisodeNumber + ); + + _searchHistoryProvider.Add(searchResult); + + if (searchResult.SearchHistoryItems.Any(r => r.Success)) + return true; + } + else { searchResult.EpisodeId = episodeId; @@ -188,10 +214,10 @@ public virtual bool EpisodeSearch(ProgressNotification notification, int episode _searchHistoryProvider.Add(searchResult); if (searchResult.SearchHistoryItems.Any(r => r.Success)) - return true; + return true; } - Logger.Warn("Unable to find {0} in any of indexers.", episode); + _logger.Warn("Unable to find {0} in any of indexers.", episode); if (reports.Any()) { @@ -250,7 +276,7 @@ public List PerformSearch(ProgressNotification notification, catch (Exception e) { - Logger.ErrorException("An error has occurred while fetching items from " + indexer.Name, e); + _logger.ErrorException("An error has occurred while fetching items from " + indexer.Name, e); } }); @@ -268,7 +294,7 @@ public List ProcessSearchResults(ProgressNotification notific { try { - Logger.Trace("Analysing report " + episodeParseResult); + _logger.Trace("Analysing report " + episodeParseResult); var item = new SearchHistoryItem { @@ -290,7 +316,7 @@ public List ProcessSearchResults(ProgressNotification notific //If series is null or doesn't match the series we're looking for return if (episodeParseResult.Series == null || episodeParseResult.Series.SeriesId != series.SeriesId) { - Logger.Trace("Unexpected series for search: {0}. Skipping.", episodeParseResult.CleanTitle); + _logger.Trace("Unexpected series for search: {0}. Skipping.", episodeParseResult.CleanTitle); item.SearchError = ReportRejectionType.WrongSeries; continue; } @@ -298,7 +324,7 @@ public List ProcessSearchResults(ProgressNotification notific //If SeasonNumber doesn't match or episode is not in the in the list in the parse result, skip the report. if (episodeParseResult.SeasonNumber != seasonNumber) { - Logger.Trace("Season number does not match searched season number, skipping."); + _logger.Trace("Season number does not match searched season number, skipping."); item.SearchError = ReportRejectionType.WrongSeason; continue; } @@ -306,7 +332,7 @@ public List ProcessSearchResults(ProgressNotification notific //If the EpisodeNumber was passed in and it is not contained in the parseResult, skip the report. if (episodeNumber.HasValue && !episodeParseResult.EpisodeNumbers.Contains(episodeNumber.Value)) { - Logger.Trace("Searched episode number is not contained in post, skipping."); + _logger.Trace("Searched episode number is not contained in post, skipping."); item.SearchError = ReportRejectionType.WrongEpisode; continue; } @@ -314,7 +340,7 @@ public List ProcessSearchResults(ProgressNotification notific //Make sure we haven't already downloaded a report with this episodenumber, if we have, skip the report. if (searchResult.Successes.Intersect(episodeParseResult.EpisodeNumbers).Any()) { - Logger.Trace("Episode has already been downloaded in this search, skipping."); + _logger.Trace("Episode has already been downloaded in this search, skipping."); item.SearchError = ReportRejectionType.Skipped; continue; } @@ -324,7 +350,7 @@ public List ProcessSearchResults(ProgressNotification notific item.SearchError = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult); if (item.SearchError == ReportRejectionType.None) { - Logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult); + _logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult); try { if (_downloadProvider.DownloadReport(episodeParseResult)) @@ -342,7 +368,7 @@ public List ProcessSearchResults(ProgressNotification notific } catch (Exception e) { - Logger.ErrorException("Unable to add report to download queue." + episodeParseResult, e); + _logger.ErrorException("Unable to add report to download queue." + episodeParseResult, e); notification.CurrentMessage = String.Format("Unable to add report to download queue. {0}", episodeParseResult); item.SearchError = ReportRejectionType.DownloadClientFailure; } @@ -350,7 +376,7 @@ public List ProcessSearchResults(ProgressNotification notific } catch (Exception e) { - Logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e); + _logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e); } } @@ -386,7 +412,7 @@ public List ProcessSearchResults(ProgressNotification notific continue; } - Logger.Trace("Analysing report " + episodeParseResult); + _logger.Trace("Analysing report " + episodeParseResult); //Get the matching series episodeParseResult.Series = _seriesProvider.FindSeries(episodeParseResult.CleanTitle); @@ -410,7 +436,7 @@ public List ProcessSearchResults(ProgressNotification notific item.SearchError = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult); if (item.SearchError == ReportRejectionType.None) { - Logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult); + _logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult); try { if (_downloadProvider.DownloadReport(episodeParseResult)) @@ -429,7 +455,7 @@ public List ProcessSearchResults(ProgressNotification notific } catch (Exception e) { - Logger.ErrorException("Unable to add report to download queue." + episodeParseResult, e); + _logger.ErrorException("Unable to add report to download queue." + episodeParseResult, e); notification.CurrentMessage = String.Format("Unable to add report to download queue. {0}", episodeParseResult); item.SearchError = ReportRejectionType.DownloadClientFailure; } @@ -437,7 +463,7 @@ public List ProcessSearchResults(ProgressNotification notific } catch (Exception e) { - Logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e); + _logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e); } } diff --git a/NzbDrone.Core/Providers/SeriesProvider.cs b/NzbDrone.Core/Providers/SeriesProvider.cs index ecbded829..f195de48a 100644 --- a/NzbDrone.Core/Providers/SeriesProvider.cs +++ b/NzbDrone.Core/Providers/SeriesProvider.cs @@ -238,6 +238,14 @@ public virtual void UpdateFromSeriesEditor(IList editedSeries) _database.UpdateMany(allSeries); } + public virtual void UpdateUseSceneNumbering(IEnumerable seriesIds) + { + _database.Execute("UPDATE Series SET UseSceneNumbering = 0"); + + var query = String.Format("UPDATE Series SET UseSceneNumbering = 1 WHERE SeriesId IN ({0})", String.Join(",", seriesIds)); + _database.Execute(query); + } + /// /// Cleans up the AirsTime Component from TheTVDB since it can be garbage that comes in. /// diff --git a/NzbDrone.Core/Providers/TvDbProvider.cs b/NzbDrone.Core/Providers/TvDbProvider.cs index 0bbd29b69..2019f1e48 100644 --- a/NzbDrone.Core/Providers/TvDbProvider.cs +++ b/NzbDrone.Core/Providers/TvDbProvider.cs @@ -51,30 +51,6 @@ public virtual TvdbSeries GetSeries(int id, bool loadEpisodes, bool loadActors = Logger.Debug("Fetching SeriesId'{0}' from tvdb", id); var result = _handler.GetSeries(id, TvdbLanguage.DefaultLanguage, loadEpisodes, loadActors, true, true); - //Fix American Dad's scene gongshow - if (result != null && result.Id == 73141) - { - result.Episodes = result.Episodes.Where(e => e.SeasonNumber == 0 || e.EpisodeNumber > 0).ToList(); - - var seasonOneEpisodeCount = result.Episodes.Where(e => e.SeasonNumber == 1).Count(); - var seasonOneId = result.Episodes.Where(e => e.SeasonNumber == 1).First().SeasonId; - - foreach (var episode in result.Episodes) - { - if (episode.SeasonNumber > 1) - { - if (episode.SeasonNumber == 2) - { - episode.EpisodeNumber = episode.EpisodeNumber + seasonOneEpisodeCount; - episode.SeasonId = seasonOneId; - } - - episode.SeasonNumber = episode.SeasonNumber - 1; - } - - } - } - //Remove duplicated episodes var episodes = result.Episodes.OrderByDescending(e => e.FirstAired).ThenByDescending(e => e.EpisodeName) .GroupBy(e => e.SeriesId.ToString("000000") + e.SeasonNumber.ToString("000") + e.EpisodeNumber.ToString("000")) From 7b5d57d2245b252fd0cf2cd7ff2b8c50a2b4add8 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 17 Oct 2012 17:07:16 -0700 Subject: [PATCH 07/13] Removed American Dad fix test --- .../ProviderTests/TvDbProviderTest.cs | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/NzbDrone.Core.Test/ProviderTests/TvDbProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/TvDbProviderTest.cs index 2dd003ace..413a5abc9 100644 --- a/NzbDrone.Core.Test/ProviderTests/TvDbProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/TvDbProviderTest.cs @@ -68,58 +68,5 @@ public void none_unique_season_episode_number() .Max(e => e.Count()).Should().Be(1); } - - [Test] - public void American_dad_fix() - { - //act - var result = tvDbProvider.GetSeries(73141, true); - - var seasonsNumbers = result.Episodes.Select(e => e.SeasonNumber) - .Distinct().ToList(); - - var seasons = new Dictionary>(seasonsNumbers.Count); - - foreach (var season in seasonsNumbers) - { - seasons.Add(season, result.Episodes.Where(e => e.SeasonNumber == season).ToList()); - } - - foreach (var episode in result.Episodes) - { - Console.WriteLine(episode); - } - - //assert - seasonsNumbers.Should().HaveCount(9); - seasons[1].Should().HaveCount(23); - seasons[2].Should().HaveCount(19); - seasons[3].Should().HaveCount(16); - seasons[4].Should().HaveCount(20); - seasons[5].Should().HaveCount(18); - seasons[6].Should().HaveCount(19); - seasons[7].Should().HaveCount(18); - - foreach (var season in seasons) - { - season.Value.Should().OnlyHaveUniqueItems("Season {0}", season.Key); - } - - //Make sure no episode number is skipped - foreach (var season in seasons) - { - for (int i = 1; i < season.Value.Count; i++) - { - //Skip specials, because someone decided 1,3,4,6,7,21 is how you count... - if (season.Key == 0) - continue; - - season.Value.Should().Contain(c => c.EpisodeNumber == i, "Can't find Episode S{0:00}E{1:00}", - season.Value[0].SeasonNumber, i); - } - } - - - } } } \ No newline at end of file From 5cd943893985c9d7e7228509260a9150bed47007 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 17 Oct 2012 21:15:42 -0700 Subject: [PATCH 08/13] Added job for updating XEM mappings XEM mappings will also be updated when a new series is imported --- NzbDrone.Core/CentralDispatch.cs | 1 + NzbDrone.Core/Jobs/ImportNewSeriesJob.cs | 8 ++- NzbDrone.Core/Jobs/XemUpdateJob.cs | 52 +++++++++++++++ NzbDrone.Core/Providers/XemProvider.cs | 80 +++++++++++++++--------- 4 files changed, 109 insertions(+), 32 deletions(-) create mode 100644 NzbDrone.Core/Jobs/XemUpdateJob.cs diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 733fc839c..64e631e6a 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -137,6 +137,7 @@ private void InitJobs() Kernel.Bind().To().InSingletonScope(); Kernel.Bind().To().InSingletonScope(); Kernel.Bind().To().InSingletonScope(); + Kernel.Bind().To().InSingletonScope(); Kernel.Get().Initialize(); Kernel.Get().StartTimer(30); diff --git a/NzbDrone.Core/Jobs/ImportNewSeriesJob.cs b/NzbDrone.Core/Jobs/ImportNewSeriesJob.cs index c6d0f66cb..f7a3099a0 100644 --- a/NzbDrone.Core/Jobs/ImportNewSeriesJob.cs +++ b/NzbDrone.Core/Jobs/ImportNewSeriesJob.cs @@ -22,6 +22,7 @@ public class ImportNewSeriesJob : IJob private readonly DiskScanJob _diskScanJob; private readonly BannerDownloadJob _bannerDownloadJob; private readonly SeasonProvider _seasonProvider; + private readonly XemUpdateJob _xemUpdateJob; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); @@ -30,7 +31,8 @@ public class ImportNewSeriesJob : IJob [Inject] public ImportNewSeriesJob(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, MediaFileProvider mediaFileProvider, UpdateInfoJob updateInfoJob, - DiskScanJob diskScanJob, BannerDownloadJob bannerDownloadJob,SeasonProvider seasonProvider) + DiskScanJob diskScanJob, BannerDownloadJob bannerDownloadJob, + SeasonProvider seasonProvider, XemUpdateJob xemUpdateJob) { _seriesProvider = seriesProvider; _episodeProvider = episodeProvider; @@ -39,6 +41,7 @@ public ImportNewSeriesJob(SeriesProvider seriesProvider, EpisodeProvider episode _diskScanJob = diskScanJob; _bannerDownloadJob = bannerDownloadJob; _seasonProvider = seasonProvider; + _xemUpdateJob = xemUpdateJob; } public string Name @@ -81,6 +84,9 @@ private void ScanSeries(ProgressNotification notification) //Download the banner for the new series _bannerDownloadJob.Start(notification, updatedSeries.SeriesId, 0); + //Get Scene Numbering if applicable + _xemUpdateJob.Start(notification, updatedSeries.SeriesId, 0); + notification.CurrentMessage = String.Format("{0} was successfully imported", updatedSeries.Title); } diff --git a/NzbDrone.Core/Jobs/XemUpdateJob.cs b/NzbDrone.Core/Jobs/XemUpdateJob.cs new file mode 100644 index 000000000..53904a624 --- /dev/null +++ b/NzbDrone.Core/Jobs/XemUpdateJob.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NLog; +using Ninject; +using NzbDrone.Core.Helpers; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Repository; + +namespace NzbDrone.Core.Jobs +{ + public class XemUpdateJob : IJob + { + private readonly XemProvider _xemProvider; + + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + + [Inject] + public XemUpdateJob(XemProvider xemProvider) + { + _xemProvider = xemProvider; + } + + public XemUpdateJob() + { + + } + + public string Name + { + get { return "XEM Update"; } + } + + public TimeSpan DefaultInterval + { + get { return TimeSpan.FromHours(12); } + } + + public virtual void Start(ProgressNotification notification, int targetId, int secondaryTargetId) + { + if (targetId == 0) + { + _logger.Trace("Starting XEM Update for all series"); + _xemProvider.UpdateMappings(); + } + _logger.Trace("Starting XEM Update for series: {0}", targetId); + _xemProvider.UpdateMappings(targetId); + _logger.Trace("XEM Update complete"); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/XemProvider.cs b/NzbDrone.Core/Providers/XemProvider.cs index 83381fb0a..b3e0e01b7 100644 --- a/NzbDrone.Core/Providers/XemProvider.cs +++ b/NzbDrone.Core/Providers/XemProvider.cs @@ -40,37 +40,7 @@ public virtual void UpdateMappings() foreach(var ser in wantedSeries) { - _logger.Trace("Updating scene numbering mapping for: {0}", ser.Title); - try - { - var episodesToUpdate = new List(); - var mappings = _xemCommunicationProvider.GetSceneTvdbMappings(ser.SeriesId); - - if (mappings == null) - { - _logger.Trace("Mappings for: {0} are null, skipping", ser.Title); - continue; - } - - foreach(var mapping in mappings) - { - _logger.Trace("Setting scene numbering mappings for {0} S{1:00}E{2:00}", ser.Title, mapping.Tvdb.Season, mapping.Tvdb.Episode); - - var episode = _episodeProvider.GetEpisode(ser.SeriesId, mapping.Tvdb.Season, mapping.Tvdb.Episode); - episode.AbsoluteEpisodeNumber = mapping.Scene.Absolute; - episode.SceneSeasonNumber = mapping.Scene.Season; - episode.SceneEpisodeNumber = mapping.Scene.Episode; - episodesToUpdate.Add(episode); - } - - _logger.Trace("Committing scene numbering mappings to database for: {0}", ser.Title); - _episodeProvider.UpdateEpisodes(episodesToUpdate); - } - - catch(Exception ex) - { - _logger.WarnException("Error updating scene numbering mappings for: " + ser, ex); - } + PerformUpdate(ser); } _logger.Trace("Completed scene numbering update"); @@ -82,5 +52,53 @@ public virtual void UpdateMappings() throw; } } + + public virtual void UpdateMappings(int seriesId) + { + var series = _seriesProvider.GetSeries(seriesId); + + if (series == null) + { + _logger.Trace("Series could not be found: {0}", seriesId); + return; + } + + PerformUpdate(series); + } + + public virtual void PerformUpdate(Series series) + { + _logger.Trace("Updating scene numbering mapping for: {0}", series.Title); + try + { + var episodesToUpdate = new List(); + var mappings = _xemCommunicationProvider.GetSceneTvdbMappings(series.SeriesId); + + if (mappings == null) + { + _logger.Trace("Mappings for: {0} are null, skipping", series.Title); + return; + } + + foreach (var mapping in mappings) + { + _logger.Trace("Setting scene numbering mappings for {0} S{1:00}E{2:00}", series.Title, mapping.Tvdb.Season, mapping.Tvdb.Episode); + + var episode = _episodeProvider.GetEpisode(series.SeriesId, mapping.Tvdb.Season, mapping.Tvdb.Episode); + episode.AbsoluteEpisodeNumber = mapping.Scene.Absolute; + episode.SceneSeasonNumber = mapping.Scene.Season; + episode.SceneEpisodeNumber = mapping.Scene.Episode; + episodesToUpdate.Add(episode); + } + + _logger.Trace("Committing scene numbering mappings to database for: {0}", series.Title); + _episodeProvider.UpdateEpisodes(episodesToUpdate); + } + + catch (Exception ex) + { + _logger.WarnException("Error updating scene numbering mappings for: " + series, ex); + } + } } } From 4176158fdfbc1b038c14fdd8aa6ab809f8911e39 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 17 Oct 2012 21:17:19 -0700 Subject: [PATCH 09/13] Missed files... --- NzbDrone.Core/NzbDrone.Core.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index f46817a93..34fc37f85 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -258,6 +258,7 @@ + From 1b2fe6e8423ab1818f502d1f22547f61c9613eff Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 17 Oct 2012 21:28:54 -0700 Subject: [PATCH 10/13] Fixed broken tests --- NzbDrone.Core.Test/JobTests/ImportNewSeriesJobTest.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NzbDrone.Core.Test/JobTests/ImportNewSeriesJobTest.cs b/NzbDrone.Core.Test/JobTests/ImportNewSeriesJobTest.cs index 8300b7185..9a83c768b 100644 --- a/NzbDrone.Core.Test/JobTests/ImportNewSeriesJobTest.cs +++ b/NzbDrone.Core.Test/JobTests/ImportNewSeriesJobTest.cs @@ -50,6 +50,9 @@ public void import_new_series_succesfull() Mocker.GetMock() .Setup(j => j.Start(notification, It.IsAny(), 0)); + Mocker.GetMock() + .Setup(j => j.Start(notification, It.IsAny(), 0)); + Mocker.GetMock() .Setup(j => j.Start(notification, series[0].SeriesId, 0)) .Callback(() => series[0].LastInfoSync = DateTime.Now); @@ -123,6 +126,9 @@ public void failed_import_should_not_be_stuck_in_loop() Mocker.GetMock() .Setup(s => s.GetSeriesFiles(It.IsAny())).Returns(new List()); + Mocker.GetMock() + .Setup(j => j.Start(notification, series[0].SeriesId, 0)); + //Act Mocker.Resolve().Start(notification, 0, 0); From c693f902717bb6fc72d568ce608db3aeb23474b9 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 17 Oct 2012 23:49:53 -0700 Subject: [PATCH 11/13] Fixed up issues with initial XEM implementation --- NzbDrone.Core.Test/Files/Xem/Ids.txt | 22 ++++------------- NzbDrone.Core.Test/Files/Xem/Names.txt | 24 +++++++++++++++++++ .../JobTests/ImportNewSeriesJobTest.cs | 1 - NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 5 +++- NzbDrone.Core/Jobs/XemUpdateJob.cs | 9 +++++-- .../Providers/XemCommunicationProvider.cs | 7 +++--- NzbDrone.Core/Providers/XemProvider.cs | 17 +++++++++++-- 7 files changed, 58 insertions(+), 27 deletions(-) create mode 100644 NzbDrone.Core.Test/Files/Xem/Names.txt diff --git a/NzbDrone.Core.Test/Files/Xem/Ids.txt b/NzbDrone.Core.Test/Files/Xem/Ids.txt index fb005862c..fad09850f 100644 --- a/NzbDrone.Core.Test/Files/Xem/Ids.txt +++ b/NzbDrone.Core.Test/Files/Xem/Ids.txt @@ -1,24 +1,10 @@ { "result": "success", - "data": { - "220571": [ - "Is This a Zombie? Of the Dead", - "Kore wa Zombie Desuka?", - "Kore wa Zombie Desuka? Of the Dead", - "Kore wa Zombie Desuka Of the Dead", - "Kore wa Zombie Desu ka - Of the Dead", - "Kore wa Zombie Desu ka of the Dead" - ], - "79151": [ - "Fate Stay Night", - "Fate/Zero", - "Fate Zero", - "Fate/Zero (2012)", - "Fate Zero S2", - "Fate Zero" - ] - }, + "data": [ + "73141", + "79886", + ], "message": "" } \ No newline at end of file diff --git a/NzbDrone.Core.Test/Files/Xem/Names.txt b/NzbDrone.Core.Test/Files/Xem/Names.txt new file mode 100644 index 000000000..fb005862c --- /dev/null +++ b/NzbDrone.Core.Test/Files/Xem/Names.txt @@ -0,0 +1,24 @@ +{ + + "result": "success", + "data": { + "220571": [ + "Is This a Zombie? Of the Dead", + "Kore wa Zombie Desuka?", + "Kore wa Zombie Desuka? Of the Dead", + "Kore wa Zombie Desuka Of the Dead", + "Kore wa Zombie Desu ka - Of the Dead", + "Kore wa Zombie Desu ka of the Dead" + ], + "79151": [ + "Fate Stay Night", + "Fate/Zero", + "Fate Zero", + "Fate/Zero (2012)", + "Fate Zero S2", + "Fate Zero" + ] + }, + "message": "" + +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/JobTests/ImportNewSeriesJobTest.cs b/NzbDrone.Core.Test/JobTests/ImportNewSeriesJobTest.cs index 9a83c768b..4d5a03b09 100644 --- a/NzbDrone.Core.Test/JobTests/ImportNewSeriesJobTest.cs +++ b/NzbDrone.Core.Test/JobTests/ImportNewSeriesJobTest.cs @@ -42,7 +42,6 @@ public void import_new_series_succesfull() .Setup(j => j.Start(notification, series[0].SeriesId, 0)) .Callback(() => series[0].LastDiskSync = DateTime.Now); - Mocker.GetMock() .Setup(j => j.Start(notification, series[1].SeriesId, 0)) .Callback(() => series[1].LastDiskSync = DateTime.Now); diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index c406f37b4..2f7a97268 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -335,10 +335,13 @@ Designer Always + + Always + Always - + Always diff --git a/NzbDrone.Core/Jobs/XemUpdateJob.cs b/NzbDrone.Core/Jobs/XemUpdateJob.cs index 53904a624..a64b5f54d 100644 --- a/NzbDrone.Core/Jobs/XemUpdateJob.cs +++ b/NzbDrone.Core/Jobs/XemUpdateJob.cs @@ -44,8 +44,13 @@ public virtual void Start(ProgressNotification notification, int targetId, int s _logger.Trace("Starting XEM Update for all series"); _xemProvider.UpdateMappings(); } - _logger.Trace("Starting XEM Update for series: {0}", targetId); - _xemProvider.UpdateMappings(targetId); + + else + { + _logger.Trace("Starting XEM Update for series: {0}", targetId); + _xemProvider.UpdateMappings(targetId); + } + _logger.Trace("XEM Update complete"); } } diff --git a/NzbDrone.Core/Providers/XemCommunicationProvider.cs b/NzbDrone.Core/Providers/XemCommunicationProvider.cs index 7c0d97e3a..3cbab3a12 100644 --- a/NzbDrone.Core/Providers/XemCommunicationProvider.cs +++ b/NzbDrone.Core/Providers/XemCommunicationProvider.cs @@ -32,14 +32,15 @@ public XemCommunicationProvider() public virtual List GetXemSeriesIds(string origin = "tvdb") { _logger.Trace("Fetching Series IDs from: {0}", origin); - var url = String.Format("{0}allNames?origin={1}", XEM_BASE_URL, origin); + + var url = String.Format("{0}havemap?origin={1}", XEM_BASE_URL, origin); var response =_httpProvider.DownloadString(url); CheckForFailureResult(response); - var result = JsonConvert.DeserializeObject>>(JObject.Parse(response).SelectToken("data").ToString()); + var result = JsonConvert.DeserializeObject>>(response); - return result.Keys.ToList(); + return result.Data.ToList(); } public virtual List GetSceneTvdbMappings(int id) diff --git a/NzbDrone.Core/Providers/XemProvider.cs b/NzbDrone.Core/Providers/XemProvider.cs index b3e0e01b7..3c1fed45c 100644 --- a/NzbDrone.Core/Providers/XemProvider.cs +++ b/NzbDrone.Core/Providers/XemProvider.cs @@ -36,7 +36,7 @@ public virtual void UpdateMappings() { var ids = _xemCommunicationProvider.GetXemSeriesIds(); var series = _seriesProvider.GetAllSeries(); - var wantedSeries = series.Where(s => ids.Contains(s.SeriesId)); + var wantedSeries = series.Where(s => ids.Contains(s.SeriesId)).ToList(); foreach(var ser in wantedSeries) { @@ -80,11 +80,20 @@ public virtual void PerformUpdate(Series series) return; } + var episodes = _episodeProvider.GetEpisodeBySeries(series.SeriesId); + foreach (var mapping in mappings) { _logger.Trace("Setting scene numbering mappings for {0} S{1:00}E{2:00}", series.Title, mapping.Tvdb.Season, mapping.Tvdb.Episode); - var episode = _episodeProvider.GetEpisode(series.SeriesId, mapping.Tvdb.Season, mapping.Tvdb.Episode); + var episode = episodes.SingleOrDefault(e => e.SeasonNumber == mapping.Tvdb.Season && e.EpisodeNumber == mapping.Tvdb.Episode); + + if (episode == null) + { + _logger.Trace("Information hasn't been added to TheTVDB yet, skipping."); + continue; + } + episode.AbsoluteEpisodeNumber = mapping.Scene.Absolute; episode.SceneSeasonNumber = mapping.Scene.Season; episode.SceneEpisodeNumber = mapping.Scene.Episode; @@ -93,6 +102,10 @@ public virtual void PerformUpdate(Series series) _logger.Trace("Committing scene numbering mappings to database for: {0}", series.Title); _episodeProvider.UpdateEpisodes(episodesToUpdate); + + _logger.Trace("Setting UseSceneMapping for {0}", series.Title); + series.UseSceneNumbering = true; + _seriesProvider.UpdateSeries(series); } catch (Exception ex) From 728a18f680598d2c77f8efb62a706978f9960a32 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 17 Oct 2012 23:50:36 -0700 Subject: [PATCH 12/13] Fixed searchProvider --- .../ProviderTests/SeriesProviderTest.cs | 36 ------------------- NzbDrone.Core/Providers/SearchProvider.cs | 7 +++- NzbDrone.Core/Providers/SeriesProvider.cs | 8 ----- 3 files changed, 6 insertions(+), 45 deletions(-) diff --git a/NzbDrone.Core.Test/ProviderTests/SeriesProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/SeriesProviderTest.cs index 3d66807a9..2d765676e 100644 --- a/NzbDrone.Core.Test/ProviderTests/SeriesProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/SeriesProviderTest.cs @@ -833,41 +833,5 @@ public void delete_series_should_delete_all_rows_related_to_the_series() } - - [Test] - public void Update_UseSceneNumbering_should_update_applicable_series() - { - WithRealDb(); - var series = Builder.CreateListOfSize(5) - .All() - .With(s => s.UseSceneNumbering = false) - .Build(); - - Db.InsertMany(series); - - Mocker.Resolve().UpdateUseSceneNumbering(new []{ 2, 3 }); - - var seriesResults = Db.Fetch(); - seriesResults.Single(s => s.SeriesId == 2).UseSceneNumbering.Should().BeTrue(); - seriesResults.Single(s => s.SeriesId == 3).UseSceneNumbering.Should().BeTrue(); - } - - [Test] - public void Update_UseSceneNumbering_should_not_update_other_series() - { - WithRealDb(); - var series = Builder.CreateListOfSize(5) - .All() - .With(s => s.UseSceneNumbering = false) - .Build(); - - Db.InsertMany(series); - - Mocker.Resolve().UpdateUseSceneNumbering(new [] { 2, 3 }); - - var seriesResults = Db.Fetch(); - seriesResults.Where(s => !s.UseSceneNumbering).Should().HaveCount(3); - seriesResults.Where(s => !s.UseSceneNumbering).Should().NotContain(s => s.SeriesId == 2 || s.SeriesId == 3); - } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/SearchProvider.cs b/NzbDrone.Core/Providers/SearchProvider.cs index 510ee312c..498930aa9 100644 --- a/NzbDrone.Core/Providers/SearchProvider.cs +++ b/NzbDrone.Core/Providers/SearchProvider.cs @@ -254,7 +254,12 @@ public List PerformSearch(ProgressNotification notification, //Treat as single episode else if (episodes.Count == 1) { - if (!series.IsDaily) + //Use SceneNumbering + if (series.UseSceneNumbering) + reports.AddRange(indexer.FetchEpisode(title, episodes.First().SceneSeasonNumber, episodes.First().SceneEpisodeNumber)); + + //Standard + else if (!series.IsDaily) reports.AddRange(indexer.FetchEpisode(title, seasonNumber, episodes.First().EpisodeNumber)); //Daily Episode diff --git a/NzbDrone.Core/Providers/SeriesProvider.cs b/NzbDrone.Core/Providers/SeriesProvider.cs index f195de48a..ecbded829 100644 --- a/NzbDrone.Core/Providers/SeriesProvider.cs +++ b/NzbDrone.Core/Providers/SeriesProvider.cs @@ -238,14 +238,6 @@ public virtual void UpdateFromSeriesEditor(IList editedSeries) _database.UpdateMany(allSeries); } - public virtual void UpdateUseSceneNumbering(IEnumerable seriesIds) - { - _database.Execute("UPDATE Series SET UseSceneNumbering = 0"); - - var query = String.Format("UPDATE Series SET UseSceneNumbering = 1 WHERE SeriesId IN ({0})", String.Join(",", seriesIds)); - _database.Execute(query); - } - /// /// Cleans up the AirsTime Component from TheTVDB since it can be garbage that comes in. /// From d99415bc31077e257875e1e855537c4d129d8aaa Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 18 Oct 2012 08:33:18 -0700 Subject: [PATCH 13/13] Revert to standard numbering when scene is absent --- .../SearchProviderTests/SearchFixture.cs | 24 ----------------- NzbDrone.Core/Providers/EpisodeProvider.cs | 4 +-- NzbDrone.Core/Providers/SearchProvider.cs | 26 ++++++++++--------- 3 files changed, 16 insertions(+), 38 deletions(-) diff --git a/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/SearchFixture.cs b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/SearchFixture.cs index 8f34f2c9a..2f23e3ed8 100644 --- a/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/SearchFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/SearchFixture.cs @@ -206,29 +206,5 @@ public void EpisodeSearch_should_skip_if_air_date_is_null_and_is_a_daily_series( result.Should().BeFalse(); ExceptionVerification.ExpectedWarns(1); } - - [Test] - public void EpisodeSearch_should_skip_if_sceneNumbering_is_invalid_and_should_use_sceneNumbering() - { - //Setup - _series.UseSceneNumbering = true; - var episode = _episodes.First(); - episode.SceneSeasonNumber = 0; - episode.SceneEpisodeNumber = 0; - episode.Series = _series; - - Mocker.GetMock().Setup(s => s.IsSatisfiedBy(It.IsAny())) - .Returns(true); - - Mocker.GetMock().Setup(s => s.GetEpisode(episode.EpisodeId)) - .Returns(episode); - - //Act - var result = Mocker.Resolve().EpisodeSearch(MockNotification, episode.EpisodeId); - - //Assert - result.Should().BeFalse(); - ExceptionVerification.ExpectedWarns(1); - } } } diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index c30000176..1e44607aa 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -188,12 +188,12 @@ public virtual IList GetEpisodesByParseResult(EpisodeParseResult parseR foreach (var episodeNumber in parseResult.EpisodeNumbers) { - Episode episodeInfo; + Episode episodeInfo = null; if (parseResult.SceneSource && parseResult.Series.UseSceneNumbering) episodeInfo = GetEpisodeBySceneNumbering(parseResult.Series.SeriesId, parseResult.SeasonNumber, episodeNumber); - else + if (episodeInfo == null) { episodeInfo = GetEpisode(parseResult.Series.SeriesId, parseResult.SeasonNumber, episodeNumber); if (episodeInfo == null && parseResult.AirDate != null) diff --git a/NzbDrone.Core/Providers/SearchProvider.cs b/NzbDrone.Core/Providers/SearchProvider.cs index 498930aa9..bf4bbea51 100644 --- a/NzbDrone.Core/Providers/SearchProvider.cs +++ b/NzbDrone.Core/Providers/SearchProvider.cs @@ -161,14 +161,6 @@ public virtual bool EpisodeSearch(ProgressNotification notification, int episode return false; } - if (episode.Series.UseSceneNumbering && episode.SceneSeasonNumber <= 0 && episode.SceneEpisodeNumber <= 0) - { - _logger.Warn("Series should use Scene Numbering, but it is not available: {0}", episode); - - notification.CurrentMessage = String.Format("Search Failed, invalid scene episode data found: {0}", episode); - return false; - } - var searchResult = new SearchHistory { SearchTime = DateTime.Now, @@ -192,13 +184,23 @@ public virtual bool EpisodeSearch(ProgressNotification notification, int episode else if (episode.Series.UseSceneNumbering) { searchResult.EpisodeId = episodeId; + + var seasonNumber = episode.SceneSeasonNumber; + var episodeNumber = episode.SceneEpisodeNumber; + + if (seasonNumber == 0 || episodeNumber == 0) + { + seasonNumber = episode.SeasonNumber; + episodeNumber = episode.EpisodeNumber; + } + searchResult.SearchHistoryItems = ProcessSearchResults( notification, reports, searchResult, episode.Series, - episode.SceneSeasonNumber, - episode.SceneEpisodeNumber + seasonNumber, + episodeNumber ); _searchHistoryProvider.Add(searchResult); @@ -254,8 +256,8 @@ public List PerformSearch(ProgressNotification notification, //Treat as single episode else if (episodes.Count == 1) { - //Use SceneNumbering - if (series.UseSceneNumbering) + //Use SceneNumbering - Only if SceneSN and SceneEN are greater than zero + if (series.UseSceneNumbering && episodes.First().SceneSeasonNumber > 0 && episodes.First().SceneEpisodeNumber > 0) reports.AddRange(indexer.FetchEpisode(title, episodes.First().SceneSeasonNumber, episodes.First().SceneEpisodeNumber)); //Standard