diff --git a/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs new file mode 100644 index 000000000..f3019c3ad --- /dev/null +++ b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Download; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests +{ + [TestFixture] + public class DownloadApprovedFixture : CoreTest + { + private Episode GetEpisode(int id) + { + return Builder.CreateNew() + .With(e => e.Id = id) + .With(e => e.EpisodeNumber = id) + .Build(); + } + + private RemoteEpisode GetRemoteEpisode(List episodes, QualityModel quality) + { + var remoteEpisode = new RemoteEpisode(); + remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo(); + remoteEpisode.ParsedEpisodeInfo.Quality = quality; + + remoteEpisode.Episodes = new List(); + remoteEpisode.Episodes.AddRange(episodes); + + remoteEpisode.Report = new ReportInfo(); + remoteEpisode.Report.Age = 0; + + return remoteEpisode; + } + + [Test] + public void should_download_report_if_epsiode_was_not_already_downloaded() + { + var episodes = new List { GetEpisode(1) }; + var remoteEpisode = GetRemoteEpisode(episodes, new QualityModel(Quality.HDTV720p)); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode)); + + Subject.DownloadApproved(decisions); + Mocker.GetMock().Verify(v => v.DownloadReport(It.IsAny()), Times.Once()); + } + + [Test] + public void should_only_download_episode_once() + { + var episodes = new List { GetEpisode(1) }; + var remoteEpisode = GetRemoteEpisode(episodes, new QualityModel(Quality.HDTV720p)); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode)); + decisions.Add(new DownloadDecision(remoteEpisode)); + + Subject.DownloadApproved(decisions); + Mocker.GetMock().Verify(v => v.DownloadReport(It.IsAny()), Times.Once()); + } + + [Test] + public void should_not_download_if_any_episode_was_already_downloaded() + { + var remoteEpisode1 = GetRemoteEpisode( + new List { GetEpisode(1) }, + new QualityModel(Quality.HDTV720p) + ); + + var remoteEpisode2 = GetRemoteEpisode( + new List { GetEpisode(1), GetEpisode(2) }, + new QualityModel(Quality.HDTV720p) + ); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + Subject.DownloadApproved(decisions); + Mocker.GetMock().Verify(v => v.DownloadReport(It.IsAny()), Times.Once()); + } + + [Test] + public void should_return_downloaded_reports() + { + var episodes = new List { GetEpisode(1) }; + var remoteEpisode = GetRemoteEpisode(episodes, new QualityModel(Quality.HDTV720p)); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode)); + + Subject.DownloadApproved(decisions).Should().HaveCount(1); + } + + [Test] + public void should_return_all_downloaded_reports() + { + var remoteEpisode1 = GetRemoteEpisode( + new List { GetEpisode(1) }, + new QualityModel(Quality.HDTV720p) + ); + + var remoteEpisode2 = GetRemoteEpisode( + new List { GetEpisode(2) }, + new QualityModel(Quality.HDTV720p) + ); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + Subject.DownloadApproved(decisions).Should().HaveCount(2); + } + + [Test] + public void should_only_return_downloaded_reports() + { + var remoteEpisode1 = GetRemoteEpisode( + new List { GetEpisode(1) }, + new QualityModel(Quality.HDTV720p) + ); + + var remoteEpisode2 = GetRemoteEpisode( + new List { GetEpisode(2) }, + new QualityModel(Quality.HDTV720p) + ); + + var remoteEpisode3 = GetRemoteEpisode( + new List { GetEpisode(2) }, + new QualityModel(Quality.HDTV720p) + ); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + decisions.Add(new DownloadDecision(remoteEpisode3)); + + Subject.DownloadApproved(decisions).Should().HaveCount(2); + } + + [Test] + public void should_not_add_to_downloaded_list_when_download_fails() + { + var episodes = new List { GetEpisode(1) }; + var remoteEpisode = GetRemoteEpisode(episodes, new QualityModel(Quality.HDTV720p)); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode)); + + Mocker.GetMock().Setup(s => s.DownloadReport(It.IsAny())).Throws(new Exception()); + Subject.DownloadApproved(decisions).Should().BeEmpty(); + ExceptionVerification.ExpectedWarns(1); + } + } +} diff --git a/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs new file mode 100644 index 000000000..8ef3ad236 --- /dev/null +++ b/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Download; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests +{ + [TestFixture] + public class DownloadApprovedReportsFixture : CoreTest + { + private Episode GetEpisode(int id) + { + return Builder.CreateNew() + .With(e => e.Id = id) + .With(e => e.EpisodeNumber = id) + .Build(); + } + + private RemoteEpisode GetRemoteEpisode(List episodes, QualityModel quality) + { + var remoteEpisode = new RemoteEpisode(); + remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo(); + remoteEpisode.ParsedEpisodeInfo.Quality = quality; + + remoteEpisode.Episodes = new List(); + remoteEpisode.Episodes.AddRange(episodes); + + remoteEpisode.Report = new ReportInfo(); + remoteEpisode.Report.Age = 0; + + return remoteEpisode; + } + + [Test] + public void should_return_an_empty_list_when_none_are_appproved() + { + var decisions = new List(); + decisions.Add(new DownloadDecision(null, "Failure!")); + decisions.Add(new DownloadDecision(null, "Failure!")); + + Subject.GetQualifiedReports(decisions).Should().BeEmpty(); + } + + [Test] + public void should_put_propers_before_non_propers() + { + var remoteEpisode1 = GetRemoteEpisode(new List { GetEpisode(1) }, new QualityModel(Quality.HDTV720p, false)); + var remoteEpisode2 = GetRemoteEpisode(new List { GetEpisode(1) }, new QualityModel(Quality.HDTV720p, true)); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + var qualifiedReports = Subject.GetQualifiedReports(decisions); + qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Proper.Should().BeTrue(); + } + + [Test] + public void should_put_higher_quality_before_lower() + { + var remoteEpisode1 = GetRemoteEpisode(new List { GetEpisode(1) }, new QualityModel(Quality.SDTV)); + var remoteEpisode2 = GetRemoteEpisode(new List { GetEpisode(1) }, new QualityModel(Quality.HDTV720p)); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + var qualifiedReports = Subject.GetQualifiedReports(decisions); + qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Quality.Should().Be(Quality.HDTV720p); + } + + [Test] + public void should_order_by_lowest_episode_number() + { + var remoteEpisode1 = GetRemoteEpisode(new List { GetEpisode(2) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode2 = GetRemoteEpisode(new List { GetEpisode(1) }, new QualityModel(Quality.HDTV720p)); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + var qualifiedReports = Subject.GetQualifiedReports(decisions); + qualifiedReports.First().RemoteEpisode.Episodes.First().EpisodeNumber.Should().Be(1); + } + + [Test] + public void should_order_by_lowest_episode_number_with_multiple_episodes() + { + var remoteEpisode1 = GetRemoteEpisode(new List { GetEpisode(2), GetEpisode(3) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode2 = GetRemoteEpisode(new List { GetEpisode(1), GetEpisode(2) }, new QualityModel(Quality.HDTV720p)); + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + var qualifiedReports = Subject.GetQualifiedReports(decisions); + qualifiedReports.First().RemoteEpisode.Episodes.First().EpisodeNumber.Should().Be(1); + } + + [Test] + public void should_order_by_youngest() + { + var remoteEpisode1 = GetRemoteEpisode(new List { GetEpisode(1) }, new QualityModel(Quality.HDTV720p)); + var remoteEpisode2 = GetRemoteEpisode(new List { GetEpisode(1) }, new QualityModel(Quality.HDTV720p)); + + remoteEpisode1.Report.Age = 10; + remoteEpisode2.Report.Age = 5; + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + var qualifiedReports = Subject.GetQualifiedReports(decisions); + qualifiedReports.First().RemoteEpisode.Report.Age.Should().Be(5); + } + } +} diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 9e2bb3696..ce20320ca 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -124,6 +124,8 @@ + + diff --git a/NzbDrone.Core/Download/DownloadApprovedReports.cs b/NzbDrone.Core/Download/DownloadApprovedReports.cs new file mode 100644 index 000000000..569dd31a2 --- /dev/null +++ b/NzbDrone.Core/Download/DownloadApprovedReports.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NLog; +using NzbDrone.Core.DecisionEngine; + +namespace NzbDrone.Core.Download +{ + public interface IDownloadApprovedReports + { + List DownloadApproved(List decisions); + } + + public class DownloadApprovedReports : IDownloadApprovedReports + { + private readonly IDownloadService _downloadService; + private readonly Logger _logger; + + public DownloadApprovedReports(IDownloadService downloadService, Logger logger) + { + _downloadService = downloadService; + _logger = logger; + } + + public List DownloadApproved(List decisions) + { + var qualifiedReports = GetQualifiedReports(decisions); + var downloadedReports = new List(); + + foreach (var report in qualifiedReports) + { + var remoteEpisode = report.RemoteEpisode; + + try + { + if (downloadedReports.SelectMany(r => r.RemoteEpisode.Episodes) + .Select(e => e.Id) + .ToList() + .Intersect(remoteEpisode.Episodes.Select(e => e.Id)) + .Any()) + { + continue; + } + + _downloadService.DownloadReport(remoteEpisode); + downloadedReports.Add(report); + } + catch (Exception e) + { + _logger.WarnException("Couldn't add report to download queue. " + remoteEpisode, e); + } + } + + return downloadedReports; + } + + public List GetQualifiedReports(List decisions) + { + return decisions.Where(c => c.Approved) + .OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality) + .ThenBy(c => c.RemoteEpisode.Episodes.Select(e => e.EpisodeNumber).MinOrDefault()) + .ThenBy(c => c.RemoteEpisode.Report.Age) + .ToList(); + } + } +} diff --git a/NzbDrone.Core/Download/DownloadApprovedReportsService.cs b/NzbDrone.Core/Download/DownloadApprovedReportsService.cs deleted file mode 100644 index 579f05828..000000000 --- a/NzbDrone.Core/Download/DownloadApprovedReportsService.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using NLog; -using NzbDrone.Core.DecisionEngine; - -namespace NzbDrone.Core.Download -{ - public interface IDownloadApprovedReportsService - { - List DownloadApproved(List decisions); - } - - public class DownloadApprovedReportsService : IDownloadApprovedReportsService - { - private readonly IDownloadService _downloadService; - private readonly Logger _logger; - - public DownloadApprovedReportsService(IDownloadService downloadService, Logger logger) - { - _downloadService = downloadService; - _logger = logger; - } - - public List DownloadApproved(List decisions) - { - var qualifiedReports = decisions - .Where(c => c.Approved) - .ToList(); - - var remoteEpisodes = qualifiedReports - .Select(c => c.RemoteEpisode) - .OrderByDescending(c => c.ParsedEpisodeInfo.Quality) - .ThenBy(c => c.Episodes.Select(e => e.EpisodeNumber).MinOrDefault()) - .ThenBy(c => c.Report.Age) - .ToList(); - - var downloadedReports = new List(); - - foreach (var episodeParseResult in remoteEpisodes) - { - try - { - if (downloadedReports.Intersect(episodeParseResult.Episodes.Select(e => e.Id)).Any()) continue; - - _downloadService.DownloadReport(episodeParseResult); - downloadedReports.AddRange(episodeParseResult.Episodes.Select(e => e.Id)); - } - catch (Exception e) - { - _logger.WarnException("Couldn't add report to download queue. " + episodeParseResult, e); - } - } - - return qualifiedReports; - } - } -} diff --git a/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs b/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs index e56079c7a..9fdbe05b1 100644 --- a/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs +++ b/NzbDrone.Core/IndexerSearch/SeasonSearchService.cs @@ -10,18 +10,18 @@ namespace NzbDrone.Core.IndexerSearch public class SeasonSearchService : IExecute { private readonly ISearchForNzb _nzbSearchService; - private readonly IDownloadApprovedReportsService _downloadApprovedReportsService; + private readonly IDownloadApprovedReports _downloadApprovedReports; - public SeasonSearchService(ISearchForNzb nzbSearchService, IDownloadApprovedReportsService downloadApprovedReportsService) + public SeasonSearchService(ISearchForNzb nzbSearchService, IDownloadApprovedReports downloadApprovedReports) { _nzbSearchService = nzbSearchService; - _downloadApprovedReportsService = downloadApprovedReportsService; + _downloadApprovedReports = downloadApprovedReports; } public void Execute(SeasonSearchCommand message) { var decisions = _nzbSearchService.SeasonSearch(message.SeriesId, message.SeasonNumber); - var qualified = _downloadApprovedReportsService.DownloadApproved(decisions); + _downloadApprovedReports.DownloadApproved(decisions); } } } diff --git a/NzbDrone.Core/Indexers/RssSyncService.cs b/NzbDrone.Core/Indexers/RssSyncService.cs index a581f2244..aa5221105 100644 --- a/NzbDrone.Core/Indexers/RssSyncService.cs +++ b/NzbDrone.Core/Indexers/RssSyncService.cs @@ -17,17 +17,17 @@ namespace NzbDrone.Core.Indexers { private readonly IFetchAndParseRss _rssFetcherAndParser; private readonly IMakeDownloadDecision _downloadDecisionMaker; - private readonly IDownloadApprovedReportsService _downloadApprovedReportsService; + private readonly IDownloadApprovedReports _downloadApprovedReports; private readonly Logger _logger; public RssSyncService(IFetchAndParseRss rssFetcherAndParser, IMakeDownloadDecision downloadDecisionMaker, - IDownloadApprovedReportsService downloadApprovedReportsService, + IDownloadApprovedReports downloadApprovedReports, Logger logger) { _rssFetcherAndParser = rssFetcherAndParser; _downloadDecisionMaker = downloadDecisionMaker; - _downloadApprovedReportsService = downloadApprovedReportsService; + _downloadApprovedReports = downloadApprovedReports; _logger = logger; } @@ -38,9 +38,9 @@ namespace NzbDrone.Core.Indexers var reports = _rssFetcherAndParser.Fetch(); var decisions = _downloadDecisionMaker.GetRssDecision(reports); - var qualifiedReports = _downloadApprovedReportsService.DownloadApproved(decisions); + var qualifiedReports = _downloadApprovedReports.DownloadApproved(decisions); - _logger.Info("RSS Sync Completed. Reports found: {0}, Fetches attempted: {1}", reports.Count, qualifiedReports.Count()); + _logger.Info("RSS Sync Completed. Reports found: {0}, Reports downloaded: {1}", reports.Count, qualifiedReports.Count()); } public void Execute(RssSyncCommand message) diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index ad9d02eaa..a736c8aae 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -246,7 +246,7 @@ - +