1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2024-12-16 11:37:58 +02:00

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.
This commit is contained in:
Taloth Saldono 2014-07-15 21:03:53 +02:00
parent d8543ad533
commit 8f192e635f
12 changed files with 151 additions and 73 deletions

View File

@ -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<ReleaseResource>
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<ReleaseResource>
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<ReleaseResource> 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<ReleaseResource> GetRss()
{
var reports = _rssFetcherAndParser.Fetch();
var decisions = _downloadDecisionMaker.GetRssDecision(reports);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
return MapDecisions(decisions);
return MapDecisions(prioritizedDecisions);
}
private static List<ReleaseResource> MapDecisions(IEnumerable<DownloadDecision> decisions)
@ -102,6 +109,18 @@ private static List<ReleaseResource> MapDecisions(IEnumerable<DownloadDecision>
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);
}

View File

@ -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; }

View File

@ -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<DownloadApprovedReports>
public class PrioritizeDownloadDecisionFixture : CoreTest<DownloadDecisionPriorizationService>
{
private Episode GetEpisode(int id)
{
@ -44,16 +45,6 @@ private RemoteEpisode GetRemoteEpisode(List<Episode> episodes, QualityModel qual
return remoteEpisode;
}
[Test]
public void should_return_an_empty_list_when_none_are_appproved()
{
var decisions = new List<DownloadDecision>();
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);
}
}

View File

@ -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<DownloadApprovedReports>
{
[SetUp]
public void SetUp()
{
Mocker.GetMock<IPrioritizeDownloadDecision>()
.Setup(v => v.PrioritizeDecisions(It.IsAny<List<DownloadDecision>>()))
.Returns<List<DownloadDecision>>(v => v);
}
private Episode GetEpisode(int id)
{
return Builder<Episode>.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<DownloadDecision>();
decisions.Add(new DownloadDecision(null, "Failure!"));
decisions.Add(new DownloadDecision(null, "Failure!"));
Subject.GetQualifiedReports(decisions).Should().BeEmpty();
}
}
}

View File

@ -114,7 +114,7 @@
<Compile Include="DecisionEngineTests\RssSync\ProperSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\Search\SeriesSpecificationFixture.cs" />
<Compile Include="Download\DownloadApprovedReportsTests\DownloadApprovedFixture.cs" />
<Compile Include="Download\DownloadApprovedReportsTests\GetQualifiedReportsFixture.cs" />
<Compile Include="DecisionEngineTests\PrioritizeDownloadDecisionFixture.cs" />
<Compile Include="Download\DownloadClientTests\Blackhole\UsenetBlackholeFixture.cs" />
<Compile Include="Download\DownloadClientTests\DownloadClientFixtureBase.cs" />
<Compile Include="Download\DownloadClientTests\NzbgetTests\NzbgetFixture.cs" />

View File

@ -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
{

View File

@ -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<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> decisions);
}
public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision
{
public List<DownloadDecision> PrioritizeDecisions(List<DownloadDecision> 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();
}
}
}

View File

@ -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<DownloadDecision> DownloadApproved(List<DownloadDecision> decisions)
{
var qualifiedReports = GetQualifiedReports(decisions);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(qualifiedReports);
var downloadedReports = new List<DownloadDecision>();
foreach (var report in qualifiedReports)
foreach (var report in prioritizedDecisions)
{
var remoteEpisode = report.RemoteEpisode;
@ -57,14 +61,7 @@ public List<DownloadDecision> DownloadApproved(List<DownloadDecision> decisions)
public List<DownloadDecision> GetQualifiedReports(IEnumerable<DownloadDecision> 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();
}
}
}

View File

@ -223,6 +223,7 @@
<Compile Include="Datastore\PagingSpec.cs" />
<Compile Include="Datastore\TableMapping.cs" />
<Compile Include="DecisionEngine\Specifications\RetrySpecification.cs" />
<Compile Include="DecisionEngine\DownloadDecisionPriorizationService.cs" />
<Compile Include="DecisionEngine\Specifications\BlacklistSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\DownloadDecision.cs" />
<Compile Include="DecisionEngine\IRejectWithReason.cs" />

View File

@ -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<IProcessProvider>().Verify(c => c.SpawnNewProcess("c:\\NzbDrone\\NzbDrone.Console.exe", StartupContext.NO_BROWSER), Times.Once());
Mocker.GetMock<IProcessProvider>().Verify(c => c.SpawnNewProcess("c:\\NzbDrone\\NzbDrone.Console.exe", "--" + StartupContext.NO_BROWSER), Times.Once());
ExceptionVerification.ExpectedWarns(1);
}

View File

@ -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
}
],

View File

@ -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;
});