From 8f192e635fe70be0d6e878d61853bd0e99b5832d Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Tue, 15 Jul 2014 21:03:53 +0200 Subject: [PATCH] New: The Manual Search result table is now sorted by the internal prioritization logic and sorting by quality now works as well. Tnx to mspec and betrayed for averting catastrophe. --- src/NzbDrone.Api/Indexers/ReleaseModule.cs | 23 +++++++++- src/NzbDrone.Api/Indexers/ReleaseResource.cs | 2 + .../PrioritizeDownloadDecisionFixture.cs} | 43 +++++++---------- .../DownloadApprovedFixture.cs | 19 ++++++++ .../NzbDrone.Core.Test.csproj | 2 +- .../DecisionEngine/DownloadDecisionMaker.cs | 3 +- .../DownloadDecisionPriorizationService.cs | 31 +++++++++++++ .../Download/DownloadApprovedReports.cs | 17 +++---- src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + .../StartNzbDroneService.cs | 2 +- src/UI/Episode/Search/ManualLayout.js | 46 ++++++++----------- src/UI/Release/ReleaseCollection.js | 35 ++++++++++++-- 12 files changed, 151 insertions(+), 73 deletions(-) rename src/NzbDrone.Core.Test/{Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs => DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs} (87%) create mode 100644 src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs diff --git a/src/NzbDrone.Api/Indexers/ReleaseModule.cs b/src/NzbDrone.Api/Indexers/ReleaseModule.cs index 2adcde6e3..0e01fada3 100644 --- a/src/NzbDrone.Api/Indexers/ReleaseModule.cs +++ b/src/NzbDrone.Api/Indexers/ReleaseModule.cs @@ -15,6 +15,7 @@ using System.Linq; using Nancy.ModelBinding; using NzbDrone.Api.Extensions; +using NzbDrone.Core.Qualities; namespace NzbDrone.Api.Indexers { @@ -23,6 +24,7 @@ public class ReleaseModule : NzbDroneRestModule private readonly IFetchAndParseRss _rssFetcherAndParser; private readonly ISearchForNzb _nzbSearchService; private readonly IMakeDownloadDecision _downloadDecisionMaker; + private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision; private readonly IDownloadService _downloadService; private readonly IParsingService _parsingService; private readonly Logger _logger; @@ -30,6 +32,7 @@ public class ReleaseModule : NzbDroneRestModule public ReleaseModule(IFetchAndParseRss rssFetcherAndParser, ISearchForNzb nzbSearchService, IMakeDownloadDecision downloadDecisionMaker, + IPrioritizeDownloadDecision prioritizeDownloadDecision, IDownloadService downloadService, IParsingService parsingService, Logger logger) @@ -37,6 +40,7 @@ public ReleaseModule(IFetchAndParseRss rssFetcherAndParser, _rssFetcherAndParser = rssFetcherAndParser; _nzbSearchService = nzbSearchService; _downloadDecisionMaker = downloadDecisionMaker; + _prioritizeDownloadDecision = prioritizeDownloadDecision; _downloadService = downloadService; _parsingService = parsingService; _logger = logger; @@ -70,7 +74,9 @@ private List GetEpisodeReleases(int episodeId) try { var decisions = _nzbSearchService.EpisodeSearch(episodeId); - return MapDecisions(decisions); + var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); + + return MapDecisions(prioritizedDecisions); } catch (Exception ex) { @@ -84,8 +90,9 @@ private List GetRss() { var reports = _rssFetcherAndParser.Fetch(); var decisions = _downloadDecisionMaker.GetRssDecision(reports); + var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); - return MapDecisions(decisions); + return MapDecisions(prioritizedDecisions); } private static List MapDecisions(IEnumerable decisions) @@ -102,6 +109,18 @@ private static List MapDecisions(IEnumerable release.Rejections = downloadDecision.Rejections.ToList(); release.DownloadAllowed = downloadDecision.RemoteEpisode.DownloadAllowed; + release.ReleaseWeight = result.Count; + + if (downloadDecision.RemoteEpisode.Series != null) + { + release.QualityWeight = downloadDecision.RemoteEpisode.Series.QualityProfile.Value.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 2; + } + + if (!release.Quality.Proper) + { + release.QualityWeight++; + } + result.Add(release); } diff --git a/src/NzbDrone.Api/Indexers/ReleaseResource.cs b/src/NzbDrone.Api/Indexers/ReleaseResource.cs index 904ffb162..bb6f53379 100644 --- a/src/NzbDrone.Api/Indexers/ReleaseResource.cs +++ b/src/NzbDrone.Api/Indexers/ReleaseResource.cs @@ -10,6 +10,7 @@ namespace NzbDrone.Api.Indexers public class ReleaseResource : RestResource { public QualityModel Quality { get; set; } + public Int32 QualityWeight { get; set; } public Int32 Age { get; set; } public Double AgeHours { get; set; } public Int64 Size { get; set; } @@ -35,6 +36,7 @@ public class ReleaseResource : RestResource public String InfoUrl { get; set; } public Boolean DownloadAllowed { get; set; } public DownloadProtocol DownloadProtocol { get; set; } + public Int32 ReleaseWeight { get; set; } public Boolean IsDaily { get; set; } public Boolean IsAbsoluteNumbering { get; set; } diff --git a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs similarity index 87% rename from src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs rename to src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs index 850421940..a4c62160d 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/GetQualifiedReportsFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs @@ -1,20 +1,21 @@ using System; using System.Collections.Generic; using System.Linq; -using FizzWare.NBuilder; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.DecisionEngine.Specifications; -using NzbDrone.Core.Download; -using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; +using NzbDrone.Core.Download; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.DecisionEngine.Specifications; +using NUnit.Framework; +using FluentAssertions; +using FizzWare.NBuilder; +using NzbDrone.Core.Test.Framework; -namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests +namespace NzbDrone.Core.Test.DecisionEngineTests { [TestFixture] - public class GetQualifiedReportsFixture : CoreTest + public class PrioritizeDownloadDecisionFixture : CoreTest { private Episode GetEpisode(int id) { @@ -44,16 +45,6 @@ private RemoteEpisode GetRemoteEpisode(List episodes, QualityModel qual 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() { @@ -64,7 +55,7 @@ public void should_put_propers_before_non_propers() decisions.Add(new DownloadDecision(remoteEpisode1)); decisions.Add(new DownloadDecision(remoteEpisode2)); - var qualifiedReports = Subject.GetQualifiedReports(decisions); + var qualifiedReports = Subject.PrioritizeDecisions(decisions); qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Proper.Should().BeTrue(); } @@ -78,7 +69,7 @@ public void should_put_higher_quality_before_lower() decisions.Add(new DownloadDecision(remoteEpisode1)); decisions.Add(new DownloadDecision(remoteEpisode2)); - var qualifiedReports = Subject.GetQualifiedReports(decisions); + var qualifiedReports = Subject.PrioritizeDecisions(decisions); qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Quality.Should().Be(Quality.HDTV720p); } @@ -92,7 +83,7 @@ public void should_order_by_lowest_number_of_episodes() decisions.Add(new DownloadDecision(remoteEpisode1)); decisions.Add(new DownloadDecision(remoteEpisode2)); - var qualifiedReports = Subject.GetQualifiedReports(decisions); + var qualifiedReports = Subject.PrioritizeDecisions(decisions); qualifiedReports.First().RemoteEpisode.Episodes.First().EpisodeNumber.Should().Be(1); } @@ -106,7 +97,7 @@ public void should_order_by_lowest_number_of_episodes_with_multiple_episodes() decisions.Add(new DownloadDecision(remoteEpisode1)); decisions.Add(new DownloadDecision(remoteEpisode2)); - var qualifiedReports = Subject.GetQualifiedReports(decisions); + var qualifiedReports = Subject.PrioritizeDecisions(decisions); qualifiedReports.First().RemoteEpisode.Episodes.First().EpisodeNumber.Should().Be(1); } @@ -125,7 +116,7 @@ public void should_order_by_smallest_rounded_to_200mb_then_age() decisions.Add(new DownloadDecision(remoteEpisodeHdSmallYounge)); decisions.Add(new DownloadDecision(remoteEpisodeHdLargeYounge)); - var qualifiedReports = Subject.GetQualifiedReports(decisions); + var qualifiedReports = Subject.PrioritizeDecisions(decisions); qualifiedReports.First().RemoteEpisode.Should().Be(remoteEpisodeHdSmallYounge); } @@ -140,7 +131,7 @@ public void should_order_by_youngest() decisions.Add(new DownloadDecision(remoteEpisode1)); decisions.Add(new DownloadDecision(remoteEpisode2)); - var qualifiedReports = Subject.GetQualifiedReports(decisions); + var qualifiedReports = Subject.PrioritizeDecisions(decisions); qualifiedReports.First().RemoteEpisode.Should().Be(remoteEpisode2); } } diff --git a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs index 0563db94e..ed80e2c3c 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadApprovedReportsTests/DownloadApprovedFixture.cs @@ -11,12 +11,21 @@ using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; +using NzbDrone.Core.DecisionEngine; namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests { [TestFixture] public class DownloadApprovedFixture : CoreTest { + [SetUp] + public void SetUp() + { + Mocker.GetMock() + .Setup(v => v.PrioritizeDecisions(It.IsAny>())) + .Returns>(v => v); + } + private Episode GetEpisode(int id) { return Builder.CreateNew() @@ -163,5 +172,15 @@ public void should_not_add_to_downloaded_list_when_download_fails() Subject.DownloadApproved(decisions).Should().BeEmpty(); ExceptionVerification.ExpectedWarns(1); } + + [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(); + } } } diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 1b71b5e0f..59e3a0599 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -114,7 +114,7 @@ - + diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index 15d964401..3babd682b 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -3,12 +3,13 @@ using System.Linq; using NLog; using NzbDrone.Common; +using NzbDrone.Common.Serializer; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Instrumentation.Extensions; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -using NzbDrone.Common.Serializer; +using NzbDrone.Core.Qualities; namespace NzbDrone.Core.DecisionEngine { diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs new file mode 100644 index 000000000..2980a1132 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; +using System.Text; +using System.Collections.Generic; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.DecisionEngine.Specifications; + +namespace NzbDrone.Core.DecisionEngine +{ + public interface IPrioritizeDownloadDecision + { + List PrioritizeDecisions(List decisions); + } + + public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision + { + public List PrioritizeDecisions(List decisions) + { + return decisions + .Where(c => c.RemoteEpisode.Series != null) + .GroupBy(c => c.RemoteEpisode.Series.Id, (i, s) => s + .OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality, new QualityModelComparer(s.First().RemoteEpisode.Series.QualityProfile)) + .ThenBy(c => c.RemoteEpisode.Episodes.Select(e => e.EpisodeNumber).MinOrDefault()) + .ThenBy(c => c.RemoteEpisode.Release.Size.Round(200.Megabytes()) / c.RemoteEpisode.Episodes.Count) + .ThenBy(c => c.RemoteEpisode.Release.Age)) + .SelectMany(c => c) + .Union(decisions.Where(c => c.RemoteEpisode.Series == null)) + .ToList(); + } + } +} diff --git a/src/NzbDrone.Core/Download/DownloadApprovedReports.cs b/src/NzbDrone.Core/Download/DownloadApprovedReports.cs index 1c8ef160b..c5dd7ef58 100644 --- a/src/NzbDrone.Core/Download/DownloadApprovedReports.cs +++ b/src/NzbDrone.Core/Download/DownloadApprovedReports.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NLog; +using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Qualities; @@ -15,20 +16,23 @@ public interface IDownloadApprovedReports public class DownloadApprovedReports : IDownloadApprovedReports { private readonly IDownloadService _downloadService; + private readonly IPrioritizeDownloadDecision _prioritizeDownloadDecision; private readonly Logger _logger; - public DownloadApprovedReports(IDownloadService downloadService, Logger logger) + public DownloadApprovedReports(IDownloadService downloadService, IPrioritizeDownloadDecision prioritizeDownloadDecision, Logger logger) { _downloadService = downloadService; + _prioritizeDownloadDecision = prioritizeDownloadDecision; _logger = logger; } public List DownloadApproved(List decisions) { var qualifiedReports = GetQualifiedReports(decisions); + var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports); var downloadedReports = new List(); - foreach (var report in qualifiedReports) + foreach (var report in prioritizedDecisions) { var remoteEpisode = report.RemoteEpisode; @@ -57,14 +61,7 @@ public List DownloadApproved(List decisions) public List GetQualifiedReports(IEnumerable decisions) { - return decisions.Where(c => c.Approved && c.RemoteEpisode.Episodes.Any()) - .GroupBy(c => c.RemoteEpisode.Series.Id, (i,s) => s - .OrderByDescending(c => c.RemoteEpisode.ParsedEpisodeInfo.Quality, new QualityModelComparer(s.First().RemoteEpisode.Series.QualityProfile)) - .ThenBy(c => c.RemoteEpisode.Episodes.Select(e => e.EpisodeNumber).MinOrDefault()) - .ThenBy(c => c.RemoteEpisode.Release.Size.Round(200.Megabytes()) / c.RemoteEpisode.Episodes.Count) - .ThenBy(c => c.RemoteEpisode.Release.Age)) - .SelectMany(c => c) - .ToList(); + return decisions.Where(c => c.Approved && c.RemoteEpisode.Episodes.Any()).ToList(); } } } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 4638cedc3..ab803b702 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -223,6 +223,7 @@ + diff --git a/src/NzbDrone.Update.Test/StartNzbDroneService.cs b/src/NzbDrone.Update.Test/StartNzbDroneService.cs index 75fc9d917..801326c6e 100644 --- a/src/NzbDrone.Update.Test/StartNzbDroneService.cs +++ b/src/NzbDrone.Update.Test/StartNzbDroneService.cs @@ -32,7 +32,7 @@ public void should_start_console_if_app_type_was_service_but_start_failed_becaus Subject.Start(AppType.Service, targetFolder); - Mocker.GetMock().Verify(c => c.SpawnNewProcess("c:\\NzbDrone\\NzbDrone.Console.exe", StartupContext.NO_BROWSER), Times.Once()); + Mocker.GetMock().Verify(c => c.SpawnNewProcess("c:\\NzbDrone\\NzbDrone.Console.exe", "--" + StartupContext.NO_BROWSER), Times.Once()); ExceptionVerification.ExpectedWarns(1); } diff --git a/src/UI/Episode/Search/ManualLayout.js b/src/UI/Episode/Search/ManualLayout.js index 5e03caf88..d91d95007 100644 --- a/src/UI/Episode/Search/ManualLayout.js +++ b/src/UI/Episode/Search/ManualLayout.js @@ -20,48 +20,40 @@ define( columns: [ { - name : 'age', - label : 'Age', - sortable: true, - cell : AgeCell + name : 'age', + label : 'Age', + cell : AgeCell }, { - name : 'title', - label : 'Title', - sortable: true, - cell : Backgrid.StringCell.extend({ className: 'nzb-title-cell' }) + name : 'title', + label : 'Title', + cell : Backgrid.StringCell.extend({ className: 'nzb-title-cell' }) }, { - name : 'indexer', - label : 'Indexer', - sortable: true, - cell : Backgrid.StringCell + name : 'indexer', + label : 'Indexer', + cell : Backgrid.StringCell }, { - name : 'size', - label : 'Size', - sortable: true, - cell : FileSizeCell + name : 'size', + label : 'Size', + cell : FileSizeCell }, { name : 'quality', label : 'Quality', - sortable : true, - cell : QualityCell, - sortValue : function (model) { - return model.get('quality').quality.weight; - } + cell : QualityCell }, { - name : 'rejections', - label: '', - cell : ApprovalStatusCell + name : 'rejections', + label : '', + cell : ApprovalStatusCell }, { - name : 'download', - label: '', - cell : DownloadReportCell + name : 'download', + label : '', + cell : DownloadReportCell } ], diff --git a/src/UI/Release/ReleaseCollection.js b/src/UI/Release/ReleaseCollection.js index c53ddccb7..796144502 100644 --- a/src/UI/Release/ReleaseCollection.js +++ b/src/UI/Release/ReleaseCollection.js @@ -1,19 +1,44 @@ 'use strict'; define( [ - 'backbone', - 'Release/ReleaseModel' - ], function (Backbone, ReleaseModel) { - return Backbone.Collection.extend({ + 'backbone.pageable', + 'Release/ReleaseModel', + 'Mixins/AsSortedCollection' + ], function (PagableCollection, ReleaseModel, AsSortedCollection) { + var Collection = PagableCollection.extend({ url : window.NzbDrone.ApiRoot + '/release', model: ReleaseModel, state: { - pageSize: 2000 + pageSize : 2000, + sortKey : 'download', + order : -1 + }, + + mode: 'client', + + sortMappings: { + 'quality' : { sortKey: 'qualityWeight' }, + 'rejections' : { sortValue: function (model, attr) { + var rejections = model.get('rejections'); + var releaseWeight = model.get('releaseWeight'); + + if (rejections.length !== 0) { + return releaseWeight + 1000000; + } + + return releaseWeight; + } + }, + 'download' : { sortKey: 'releaseWeight' } }, fetchEpisodeReleases: function (episodeId) { return this.fetch({ data: { episodeId: episodeId }}); } }); + + Collection = AsSortedCollection.call(Collection); + + return Collection; });