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.
pull/92/head
Taloth Saldono 10 years ago
parent d8543ad533
commit 8f192e635f

@ -15,6 +15,7 @@ using Omu.ValueInjecter;
using System.Linq;
using Nancy.ModelBinding;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.Indexers
{
@ -23,6 +24,7 @@ namespace NzbDrone.Api.Indexers
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 @@ namespace NzbDrone.Api.Indexers
public ReleaseModule(IFetchAndParseRss rssFetcherAndParser,
ISearchForNzb nzbSearchService,
IMakeDownloadDecision downloadDecisionMaker,
IPrioritizeDownloadDecision prioritizeDownloadDecision,
IDownloadService downloadService,
IParsingService parsingService,
Logger logger)
@ -37,6 +40,7 @@ namespace NzbDrone.Api.Indexers
_rssFetcherAndParser = rssFetcherAndParser;
_nzbSearchService = nzbSearchService;
_downloadDecisionMaker = downloadDecisionMaker;
_prioritizeDownloadDecision = prioritizeDownloadDecision;
_downloadService = downloadService;
_parsingService = parsingService;
_logger = logger;
@ -70,7 +74,9 @@ namespace NzbDrone.Api.Indexers
try
{
var decisions = _nzbSearchService.EpisodeSearch(episodeId);
return MapDecisions(decisions);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
return MapDecisions(prioritizedDecisions);
}
catch (Exception ex)
{
@ -84,8 +90,9 @@ namespace NzbDrone.Api.Indexers
{
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 @@ namespace NzbDrone.Api.Indexers
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);
}

@ -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 @@ namespace NzbDrone.Api.Indexers
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; }

@ -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.Tv;
using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model;
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;
using NzbDrone.Core.Tv;
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 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
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 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
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 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
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 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
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 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
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 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
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 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
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);
}
}

@ -11,12 +11,21 @@ using NzbDrone.Core.Qualities;
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 @@ namespace NzbDrone.Core.Test.Download.DownloadApprovedReportsTests
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();
}
}
}

@ -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" />

@ -3,12 +3,13 @@ using System.Collections.Generic;
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
{

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

@ -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 @@ namespace NzbDrone.Core.Download
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 @@ namespace NzbDrone.Core.Download
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();
}
}
}

@ -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" />

@ -32,7 +32,7 @@ namespace NzbDrone.Update.Test
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);
}

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

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

Loading…
Cancel
Save