From f604c35768818300f32c7beda8ccdfcc78e5592b Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 28 Aug 2011 12:07:56 -0700 Subject: [PATCH] Backlog search added (disabled) - It will search for a full season if a full season is missing. --- NzbDrone.Core.Test/BacklogSearchJobTest.cs | 203 ++++++++++++++++++ NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 1 + NzbDrone.Core/NzbDrone.Core.csproj | 1 + NzbDrone.Core/Providers/EpisodeProvider.cs | 5 + .../Providers/Jobs/BacklogSearchJob.cs | 84 ++++++++ .../Providers/Jobs/SeasonSearchJob.cs | 2 +- NzbDrone.Core/Providers/SearchProvider.cs | 9 +- 7 files changed, 298 insertions(+), 7 deletions(-) create mode 100644 NzbDrone.Core.Test/BacklogSearchJobTest.cs create mode 100644 NzbDrone.Core/Providers/Jobs/BacklogSearchJob.cs diff --git a/NzbDrone.Core.Test/BacklogSearchJobTest.cs b/NzbDrone.Core.Test/BacklogSearchJobTest.cs new file mode 100644 index 000000000..8b9eaa187 --- /dev/null +++ b/NzbDrone.Core.Test/BacklogSearchJobTest.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using AutoMoq; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Model; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Jobs; +using NzbDrone.Core.Repository; + +namespace NzbDrone.Core.Test +{ + [TestFixture] + public class BacklogSearchJobTest + { + [Test] + public void no_missing_epsiodes() + { + //Setup + var notification = new ProgressNotification("Backlog Search Job Test"); + + var episodes = new List(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes); + + //Act + mocker.Resolve().Start(notification, 0, 0); + + //Assert + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), It.IsAny()), + Times.Never()); + + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), + Times.Never()); + } + + [Test] + public void individual_missing_episode_only() + { + //Setup + var notification = new ProgressNotification("Backlog Search Job Test"); + + var episodes = Builder.CreateListOfSize(1).Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes); + + mocker.GetMock() + .Setup(s => s.Start(notification, It.IsAny(), 0)).Verifiable(); + + //Act + mocker.Resolve().Start(notification, 0, 0); + + //Assert + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), It.IsAny()), + Times.Never()); + + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), + Times.Once()); + } + + [Test] + public void individual_missing_episodes_only() + { + //Setup + var notification = new ProgressNotification("Backlog Search Job Test"); + + var episodes = Builder.CreateListOfSize(5).Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes); + + mocker.GetMock() + .Setup(s => s.Start(notification, It.IsAny(), 0)).Verifiable(); + + //Act + mocker.Resolve().Start(notification, 0, 0); + + //Assert + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), It.IsAny()), + Times.Never()); + + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), + Times.Exactly(episodes.Count)); + } + + [Test] + public void series_season_missing_episodes_only_mismatch_count() + { + //Setup + var notification = new ProgressNotification("Backlog Search Job Test"); + + var episodes = Builder.CreateListOfSize(5) + .WhereAll() + .Have(e => e.SeriesId = 1) + .Have(e => e.SeasonNumber = 1) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes); + + mocker.GetMock() + .Setup(s => s.Start(notification, It.IsAny(), 0)).Verifiable(); + + mocker.GetMock() + .Setup(s => s.GetEpisodeNumbersBySeason(1, 1)).Returns(new List {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + + //Act + mocker.Resolve().Start(notification, 0, 0); + + //Assert + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), It.IsAny()), + Times.Never()); + + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), + Times.Exactly(episodes.Count)); + } + + [Test] + public void series_season_missing_episodes_only() + { + //Setup + var notification = new ProgressNotification("Backlog Search Job Test"); + + var episodes = Builder.CreateListOfSize(5) + .WhereAll() + .Have(e => e.SeriesId = 1) + .Have(e => e.SeasonNumber = 1) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes); + + mocker.GetMock() + .Setup(s => s.Start(notification, It.IsAny(), It.IsAny())).Verifiable(); + + mocker.GetMock() + .Setup(s => s.GetEpisodeNumbersBySeason(1, 1)).Returns(episodes.Select(e => e.EpisodeNumber).ToList()); + + //Act + mocker.Resolve().Start(notification, 0, 0); + + //Assert + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), It.IsAny()), + Times.Once()); + + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), + Times.Never()); + } + + [Test] + public void multiple_missing_episodes() + { + //Setup + var notification = new ProgressNotification("Backlog Search Job Test"); + + var episodes = Builder.CreateListOfSize(10) + .WhereTheFirst(5) + .Have(e => e.SeriesId = 1) + .Have(e => e.SeasonNumber = 1) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes); + + mocker.GetMock() + .Setup(s => s.Start(notification, It.IsAny(), It.IsAny())).Verifiable(); + + mocker.GetMock() + .Setup(s => s.Start(notification, It.IsAny(), 0)).Verifiable(); + + mocker.GetMock() + .Setup(s => s.GetEpisodeNumbersBySeason(1, 1)).Returns(new List{ 1, 2, 3, 4, 5 }); + + //Act + mocker.Resolve().Start(notification, 0, 0); + + //Assert + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), It.IsAny()), + Times.Once()); + + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), + Times.Exactly(5)); + } + } +} diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index b537afc25..97f14ff4e 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -88,6 +88,7 @@ + diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 5935d1f1c..afa70eabc 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -200,6 +200,7 @@ + diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index d0cf2858b..6961b2843 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -296,6 +296,11 @@ namespace NzbDrone.Core.Providers return _database.Fetch("SELECT DISTINCT SeasonNumber FROM Episodes WHERE SeriesId=@0", seriesId).OrderBy(c => c).ToList(); } + public virtual IList GetEpisodeNumbersBySeason(int seriesId, int seasonNumber) + { + return _database.Fetch("SELECT EpisodeNumber FROM Episodes WHERE SeriesId=@0 AND SeasonNumber=@1", seriesId, seasonNumber).OrderBy(c => c).ToList(); + } + public virtual void SetSeasonIgnore(long seriesId, int seasonNumber, bool isIgnored) { Logger.Info("Setting ignore flag on Series:{0} Season:{1} to {2}", seriesId, seasonNumber, isIgnored); diff --git a/NzbDrone.Core/Providers/Jobs/BacklogSearchJob.cs b/NzbDrone.Core/Providers/Jobs/BacklogSearchJob.cs new file mode 100644 index 000000000..d008a7172 --- /dev/null +++ b/NzbDrone.Core/Providers/Jobs/BacklogSearchJob.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Core.Model; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Repository; + +namespace NzbDrone.Core.Providers.Jobs +{ + public class BacklogSearchJob : IJob + { + private readonly EpisodeProvider _episodeProvider; + private readonly EpisodeSearchJob _episodeSearchJob; + private readonly SeasonSearchJob _seasonSearchJob; + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + public BacklogSearchJob(EpisodeProvider episodeProvider, EpisodeSearchJob episodeSearchJob, + SeasonSearchJob seasonSearchJob) + { + _episodeProvider = episodeProvider; + _episodeSearchJob = episodeSearchJob; + _seasonSearchJob = seasonSearchJob; + } + + public string Name + { + get { return "Backlog Search"; } + } + + public int DefaultInterval + { + get { return 0; } + } + + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) + { + var missingEpisodes = + _episodeProvider.EpisodesWithoutFiles(true).GroupBy(e => new { e.SeriesId, e.SeasonNumber }); + + var individualEpisodes = new List(); + + Logger.Trace("Processing missing episodes list"); + foreach (var group in missingEpisodes) + { + var count = group.Count(); + + if (count == 1) + individualEpisodes.Add(group.First()); + + else + { + //Get count and compare to the actual number of episodes for this season + //If numbers don't match then add to individual episodes, else process as full season... + var seriesId = group.Key.SeriesId; + var seasonNumber = group.Key.SeasonNumber; + + var countInDb = _episodeProvider.GetEpisodeNumbersBySeason(seriesId, seasonNumber).Count; + + if (count != countInDb) + { + //Add the episodes to be processed manually + individualEpisodes.AddRange(group); + } + + else + { + //Process as a full season + Logger.Debug("Processing Full Season: {0} Season {1}", seriesId, seasonNumber); + _seasonSearchJob.Start(notification, seriesId, seasonNumber); + } + } + } + + Logger.Debug("Processing standalone episodes"); + //Process the list of remaining episodes, 1 by 1 + foreach (var episode in individualEpisodes) + { + _episodeSearchJob.Start(notification, episode.EpisodeId, 0); + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs b/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs index 68736b109..9f90c6e58 100644 --- a/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs +++ b/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs @@ -53,7 +53,7 @@ namespace NzbDrone.Core.Providers.Jobs Logger.Debug("Getting episodes from database for series: {0} and season: {1}", targetId, secondaryTargetId); var episodes = _episodeProvider.GetEpisodesBySeason(targetId, secondaryTargetId); - if (episodes == null) + if (episodes == null || episodes.Count == 0) { Logger.Warn("No episodes in database found for series: {0} and season: {1}.", targetId, secondaryTargetId); return; diff --git a/NzbDrone.Core/Providers/SearchProvider.cs b/NzbDrone.Core/Providers/SearchProvider.cs index 4605e32bc..01afb165f 100644 --- a/NzbDrone.Core/Providers/SearchProvider.cs +++ b/NzbDrone.Core/Providers/SearchProvider.cs @@ -82,17 +82,14 @@ namespace NzbDrone.Core.Providers return false; Logger.Debug("Getting episodes from database for series: {0} and season: {1}", seriesId, seasonNumber); - var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber); + var episodeNumbers = _episodeProvider.GetEpisodeNumbersBySeason(seriesId, seasonNumber); - if (episodes == null) + if (episodeNumbers == null || episodeNumbers.Count == 0) { Logger.Warn("No episodes in database found for series: {0} and season: {1}.", seriesId, seasonNumber); return false; } - var episodeNumbers = new List(); - episodeNumbers.AddRange(episodes.Select(e => e.EpisodeNumber)); - notification.CurrentMessage = "Processing search results"; var reportsToProcess = reports.Where(p => p.FullSeason && p.SeasonNumber == seasonNumber).ToList(); @@ -100,7 +97,7 @@ namespace NzbDrone.Core.Providers reportsToProcess.ForEach(c => { c.Series = series; - c.EpisodeNumbers = episodeNumbers; + c.EpisodeNumbers = episodeNumbers.ToList(); }); return ProcessSeasonSearchResults(notification, series, seasonNumber, reportsToProcess);