New: Quality Preferred Setting

This commit is contained in:
Qstick 2020-02-11 20:31:44 -05:00
parent ea18a366c4
commit 43c48bf833
12 changed files with 370 additions and 138 deletions

View File

@ -31,7 +31,7 @@
background-color: $sliderAccentColor;
box-shadow: 0 0 0 #000;
&:nth-child(odd) {
&:nth-child(3n+1) {
background-color: #ddd;
}
}
@ -56,7 +56,7 @@
.megabytesPerMinute {
display: flex;
justify-content: space-between;
flex: 0 0 250px;
flex: 0 0 400px;
}
.sizeInput {

View File

@ -48,21 +48,24 @@ class QualityDefinition extends Component {
this.state = {
sliderMinSize: getSliderValue(props.minSize, slider.min),
sliderMaxSize: getSliderValue(props.maxSize, slider.max)
sliderMaxSize: getSliderValue(props.maxSize, slider.max),
sliderPreferredSize: getSliderValue(props.preferredSize, (slider.max - 3))
};
}
//
// Listeners
onSliderChange = ([sliderMinSize, sliderMaxSize]) => {
onSliderChange = ([sliderMinSize, sliderPreferredSize, sliderMaxSize]) => {
this.setState({
sliderMinSize,
sliderMaxSize
sliderMaxSize,
sliderPreferredSize
});
this.props.onSizeChange({
minSize: roundNumber(Math.pow(sliderMinSize, 1.1)),
preferredSize: sliderPreferredSize === (slider.max - 3) ? null : roundNumber(Math.pow(sliderPreferredSize, 1.1)),
maxSize: sliderMaxSize === slider.max ? null : roundNumber(Math.pow(sliderMaxSize, 1.1))
});
}
@ -70,12 +73,14 @@ class QualityDefinition extends Component {
onAfterSliderChange = () => {
const {
minSize,
maxSize
maxSize,
preferredSize
} = this.props;
this.setState({
sliderMiSize: getSliderValue(minSize, slider.min),
sliderMaxSize: getSliderValue(maxSize, slider.max)
sliderMaxSize: getSliderValue(maxSize, slider.max),
sliderPreferredSize: getSliderValue(preferredSize, (slider.max - 3)) // fix
});
}
@ -88,7 +93,22 @@ class QualityDefinition extends Component {
this.props.onSizeChange({
minSize,
maxSize: this.props.maxSize
maxSize: this.props.maxSize,
preferredSize: this.props.preferredSize
});
}
onPreferredSizeChange = ({ value }) => {
const preferredSize = value === (MAX - 3) ? null : getValue(value);
this.setState({
sliderPreferredSize: getSliderValue(preferredSize, slider.preferred)
});
this.props.onSizeChange({
minSize: this.props.minSize,
maxSize: this.props.maxSize,
preferredSize
});
}
@ -101,7 +121,8 @@ class QualityDefinition extends Component {
this.props.onSizeChange({
minSize: this.props.minSize,
maxSize
maxSize,
preferredSize: this.props.preferredSize
});
}
@ -115,18 +136,23 @@ class QualityDefinition extends Component {
title,
minSize,
maxSize,
preferredSize,
advancedSettings,
onTitleChange
} = this.props;
const {
sliderMinSize,
sliderMaxSize
sliderMaxSize,
sliderPreferredSize
} = this.state;
const minBytes = minSize * 1024 * 1024;
const minSixty = `${formatBytes(minBytes * 60)}/h`;
const preferredBytes = preferredSize * 1024 * 1024;
const preferredSixty = preferredBytes ? `${formatBytes(preferredBytes * 60)}/h` : 'Unlimited';
const maxBytes = maxSize && maxSize * 1024 * 1024;
const maxSixty = maxBytes ? `${formatBytes(maxBytes * 60)}/h` : 'Unlimited';
@ -149,9 +175,10 @@ class QualityDefinition extends Component {
min={slider.min}
max={slider.max}
step={slider.step}
minDistance={10}
value={[sliderMinSize, sliderMaxSize]}
minDistance={3}
value={[sliderMinSize, sliderPreferredSize, sliderMaxSize]}
withTracks={true}
allowCross={false}
snapDragDisabled={true}
className={styles.slider}
trackClassName={styles.bar}
@ -177,6 +204,22 @@ class QualityDefinition extends Component {
/>
</div>
<div>
<Popover
anchor={
<Label kind={kinds.SUCCESS}>{preferredSixty}</Label>
}
title="Preferred Size"
body={
<QualityDefinitionLimits
bytes={preferredBytes}
message="No limit for any runtime"
/>
}
position={tooltipPositions.BOTTOM}
/>
</div>
<div>
<Popover
anchor={
@ -206,13 +249,28 @@ class QualityDefinition extends Component {
name={`${id}.min`}
value={minSize || MIN}
min={MIN}
max={maxSize ? maxSize - 10 : MAX - 10}
max={preferredSize ? preferredSize - 5 : MAX - 5}
step={0.1}
isFloat={true}
onChange={this.onMinSizeChange}
/>
</div>
<div>
Preferred
<NumberInput
className={styles.sizeInput}
name={`${id}.min`}
value={preferredSize || MAX - 5}
min={MIN}
max={maxSize ? maxSize - 5 : MAX - 5}
step={0.1}
isFloat={true}
onChange={this.onPreferredSizeChange}
/>
</div>
<div>
Max
@ -220,7 +278,7 @@ class QualityDefinition extends Component {
className={styles.sizeInput}
name={`${id}.min`}
value={maxSize || MAX}
min={minSize + 10}
min={minSize + 5}
max={MAX}
step={0.1}
isFloat={true}
@ -240,6 +298,7 @@ QualityDefinition.propTypes = {
title: PropTypes.string.isRequired,
minSize: PropTypes.number,
maxSize: PropTypes.number,
preferredSize: PropTypes.number,
advancedSettings: PropTypes.bool.isRequired,
onTitleChange: PropTypes.func.isRequired,
onSizeChange: PropTypes.func.isRequired

View File

@ -23,11 +23,12 @@ class QualityDefinitionConnector extends Component {
this.props.setQualityDefinitionValue({ id: this.props.id, name: 'title', value });
}
onSizeChange = ({ minSize, maxSize }) => {
onSizeChange = ({ minSize, maxSize, preferredSize }) => {
const {
id,
minSize: currentMinSize,
maxSize: currentMaxSize
maxSize: currentMaxSize,
preferredSize: currentPreferredSize
} = this.props;
if (minSize !== currentMinSize) {
@ -37,6 +38,10 @@ class QualityDefinitionConnector extends Component {
if (maxSize !== currentMaxSize) {
this.props.setQualityDefinitionValue({ id, name: 'maxSize', value: maxSize });
}
if (preferredSize !== currentPreferredSize) {
this.props.setQualityDefinitionValue({ id, name: 'preferredSize', value: preferredSize });
}
}
//
@ -57,6 +62,7 @@ QualityDefinitionConnector.propTypes = {
id: PropTypes.number.isRequired,
minSize: PropTypes.number,
maxSize: PropTypes.number,
preferredSize: PropTypes.number,
setQualityDefinitionValue: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};

View File

@ -105,7 +105,7 @@
"react-popper": "1.3.7",
"react-redux": "7.1.3",
"react-router-dom": "5.1.2",
"react-slider": "1.0.1",
"react-slider": "1.0.3",
"react-tabs": "3.0.0",
"react-text-truncate": "0.15.0",
"react-virtualized": "9.21.1",

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
@ -37,9 +37,20 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_customFormat2 = new CustomFormat("My Format 2", new LanguageSpecification { Value = (int)Language.French }) { Id = 2 };
CustomFormatsFixture.GivenCustomFormats(_customFormat1, _customFormat2);
Mocker.GetMock<IQualityDefinitionService>()
.Setup(s => s.Get(It.IsAny<Quality>()))
.Returns(new QualityDefinition { PreferredSize = null });
}
private RemoteMovie GivenRemoteMovie(QualityModel quality, int age = 0, long size = 0, DownloadProtocol downloadProtocol = DownloadProtocol.Usenet)
private void GivenPreferredSize(double? size)
{
Mocker.GetMock<IQualityDefinitionService>()
.Setup(s => s.Get(It.IsAny<Quality>()))
.Returns(new QualityDefinition { PreferredSize = size });
}
private RemoteMovie GivenRemoteMovie(QualityModel quality, int age = 0, long size = 0, DownloadProtocol downloadProtocol = DownloadProtocol.Usenet, int runtime = 150)
{
var remoteMovie = new RemoteMovie();
remoteMovie.ParsedMovieInfo = new ParsedMovieInfo();
@ -54,7 +65,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
FormatItems = CustomFormatsFixture.GetSampleFormatItems(_customFormat1.Name, _customFormat2.Name),
MinFormatScore = 0
})
.With(m => m.Title = "A Movie").Build();
.With(m => m.Title = "A Movie")
.With(m => m.Runtime = runtime).Build();
remoteMovie.Release = new ReleaseInfo();
remoteMovie.Release.PublishDate = DateTime.Now.AddDays(-age);
@ -81,12 +93,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_put_propers_before_non_propers()
{
var remoteEpisode1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p, new Revision(version: 1)));
var remoteEpisode2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p, new Revision(version: 2)));
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p, new Revision(version: 1)));
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p, new Revision(version: 2)));
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.ParsedMovieInfo.Quality.Revision.Version.Should().Be(2);
@ -95,12 +107,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_put_higher_quality_before_lower()
{
var remoteEpisode1 = GivenRemoteMovie(new QualityModel(Quality.SDTV));
var remoteEpisode2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.SDTV));
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.ParsedMovieInfo.Quality.Quality.Should().Be(Quality.HDTV720p);
@ -109,33 +121,105 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_order_by_age_then_largest_rounded_to_200mb()
{
var remoteEpisodeSd = GivenRemoteMovie(new QualityModel(Quality.SDTV), size: 100.Megabytes(), age: 1);
var remoteEpisodeHdSmallOld = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 1200.Megabytes(), age: 1000);
var remoteEpisodeSmallYoung = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 1250.Megabytes(), age: 10);
var remoteEpisodeHdLargeYoung = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 3000.Megabytes(), age: 1);
var remoteMovieSd = GivenRemoteMovie(new QualityModel(Quality.SDTV), size: 100.Megabytes(), age: 1);
var remoteMovieHdSmallOld = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 1200.Megabytes(), age: 1000);
var remoteMovieSmallYoung = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 1250.Megabytes(), age: 10);
var remoteMovieHdLargeYoung = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 3000.Megabytes(), age: 1);
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisodeSd));
decisions.Add(new DownloadDecision(remoteEpisodeHdSmallOld));
decisions.Add(new DownloadDecision(remoteEpisodeSmallYoung));
decisions.Add(new DownloadDecision(remoteEpisodeHdLargeYoung));
decisions.Add(new DownloadDecision(remoteMovieSd));
decisions.Add(new DownloadDecision(remoteMovieHdSmallOld));
decisions.Add(new DownloadDecision(remoteMovieSmallYoung));
decisions.Add(new DownloadDecision(remoteMovieHdLargeYoung));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Should().Be(remoteEpisodeHdLargeYoung);
qualifiedReports.First().RemoteMovie.Should().Be(remoteMovieHdLargeYoung);
}
[Test]
public void should_order_by_closest_to_preferred_size_if_both_over()
{
// 2 MB/Min * 150 Min Runtime = 300 MB
GivenPreferredSize(2);
var remoteMovieSmall = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 400.Megabytes(), age: 1);
var remoteMovieLarge = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 15000.Megabytes(), age: 1);
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteMovieSmall));
decisions.Add(new DownloadDecision(remoteMovieLarge));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Should().Be(remoteMovieSmall);
}
[Test]
public void should_order_by_largest_to_if_zero_runtime()
{
// 2 MB/Min * 150 Min Runtime = 300 MB
GivenPreferredSize(2);
var remoteMovieSmall = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 400.Megabytes(), age: 1, runtime: 0);
var remoteMovieLarge = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 15000.Megabytes(), age: 1, runtime: 0);
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteMovieSmall));
decisions.Add(new DownloadDecision(remoteMovieLarge));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Should().Be(remoteMovieLarge);
}
[Test]
public void should_order_by_closest_to_preferred_size_if_both_under()
{
// 390 MB/Min * 150 Min Runtime = 58,500 MB
GivenPreferredSize(390);
var remoteMovieSmall = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 100.Megabytes(), age: 1);
var remoteMovieLarge = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 15000.Megabytes(), age: 1);
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteMovieSmall));
decisions.Add(new DownloadDecision(remoteMovieLarge));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Should().Be(remoteMovieLarge);
}
[Test]
public void should_order_by_closest_to_preferred_size_if_preferred_is_in_between()
{
// 46 MB/Min * 150 Min Runtime = 6900 MB
GivenPreferredSize(46);
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 100.Megabytes(), age: 1);
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 500.Megabytes(), age: 1);
var remoteMovie3 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 7000.Megabytes(), age: 1);
var remoteMovie4 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), size: 15000.Megabytes(), age: 1);
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
decisions.Add(new DownloadDecision(remoteMovie3));
decisions.Add(new DownloadDecision(remoteMovie4));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Should().Be(remoteMovie3);
}
[Test]
public void should_order_by_youngest()
{
var remoteEpisode1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), age: 10);
var remoteEpisode2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), age: 5);
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), age: 10);
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), age: 5);
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Should().Be(remoteEpisode2);
qualifiedReports.First().RemoteMovie.Should().Be(remoteMovie2);
}
[Test]
@ -143,12 +227,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenPreferredDownloadProtocol(DownloadProtocol.Usenet);
var remoteEpisode1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Torrent);
var remoteEpisode2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Usenet);
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Torrent);
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Usenet);
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Release.DownloadProtocol.Should().Be(DownloadProtocol.Usenet);
@ -159,12 +243,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenPreferredDownloadProtocol(DownloadProtocol.Torrent);
var remoteEpisode1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Torrent);
var remoteEpisode2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Usenet);
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Torrent);
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p), downloadProtocol: DownloadProtocol.Usenet);
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Release.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
@ -173,8 +257,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_prefer_releases_with_more_seeders()
{
var remoteEpisode1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var torrentInfo1 = new TorrentInfo();
torrentInfo1.PublishDate = DateTime.Now;
@ -185,14 +269,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
var torrentInfo2 = torrentInfo1.JsonClone();
torrentInfo2.Seeders = 100;
remoteEpisode1.Release = torrentInfo1;
remoteEpisode1.Release.Title = "A Movie 1998";
remoteEpisode2.Release = torrentInfo2;
remoteEpisode2.Release.Title = "A Movie 1998";
remoteMovie1.Release = torrentInfo1;
remoteMovie1.Release.Title = "A Movie 1998";
remoteMovie2.Release = torrentInfo2;
remoteMovie2.Release.Title = "A Movie 1998";
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
((TorrentInfo)qualifiedReports.First().RemoteMovie.Release).Seeders.Should().Be(torrentInfo2.Seeders);
@ -201,8 +285,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_prefer_releases_with_more_peers_given_equal_number_of_seeds()
{
var remoteEpisode1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var torrentInfo1 = new TorrentInfo();
torrentInfo1.PublishDate = DateTime.Now;
@ -214,14 +298,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
var torrentInfo2 = torrentInfo1.JsonClone();
torrentInfo2.Peers = 100;
remoteEpisode1.Release = torrentInfo1;
remoteEpisode1.Release.Title = "A Movie 1998";
remoteEpisode2.Release = torrentInfo2;
remoteEpisode2.Release.Title = "A Movie 1998";
remoteMovie1.Release = torrentInfo1;
remoteMovie1.Release.Title = "A Movie 1998";
remoteMovie2.Release = torrentInfo2;
remoteMovie2.Release.Title = "A Movie 1998";
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
((TorrentInfo)qualifiedReports.First().RemoteMovie.Release).Peers.Should().Be(torrentInfo2.Peers);
@ -230,8 +314,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_prefer_releases_with_more_peers_no_seeds()
{
var remoteEpisode1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var torrentInfo1 = new TorrentInfo();
torrentInfo1.PublishDate = DateTime.Now;
@ -244,14 +328,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
torrentInfo2.Seeders = 0;
torrentInfo2.Peers = 100;
remoteEpisode1.Release = torrentInfo1;
remoteEpisode1.Release.Title = "A Movie 1998";
remoteEpisode2.Release = torrentInfo2;
remoteEpisode2.Release.Title = "A Movie 1998";
remoteMovie1.Release = torrentInfo1;
remoteMovie1.Release.Title = "A Movie 1998";
remoteMovie2.Release = torrentInfo2;
remoteMovie2.Release.Title = "A Movie 1998";
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
((TorrentInfo)qualifiedReports.First().RemoteMovie.Release).Peers.Should().Be(torrentInfo2.Peers);
@ -260,8 +344,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_prefer_first_release_if_peers_and_size_are_too_similar()
{
var remoteEpisode1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var torrentInfo1 = new TorrentInfo();
torrentInfo1.PublishDate = DateTime.Now;
@ -275,14 +359,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
torrentInfo2.Peers = 10;
torrentInfo1.Size = 250.Megabytes();
remoteEpisode1.Release = torrentInfo1;
remoteEpisode1.Release.Title = "A Movie 1998";
remoteEpisode2.Release = torrentInfo2;
remoteEpisode2.Release.Title = "A Movie 1998";
remoteMovie1.Release = torrentInfo1;
remoteMovie1.Release.Title = "A Movie 1998";
remoteMovie2.Release = torrentInfo2;
remoteMovie2.Release.Title = "A Movie 1998";
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
((TorrentInfo)qualifiedReports.First().RemoteMovie.Release).Should().Be(torrentInfo1);
@ -291,38 +375,38 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_prefer_first_release_if_age_and_size_are_too_similar()
{
var remoteEpisode1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
remoteEpisode1.Release.PublishDate = DateTime.UtcNow.AddDays(-100);
remoteEpisode1.Release.Size = 200.Megabytes();
remoteMovie1.Release.PublishDate = DateTime.UtcNow.AddDays(-100);
remoteMovie1.Release.Size = 200.Megabytes();
remoteEpisode2.Release.PublishDate = DateTime.UtcNow.AddDays(-150);
remoteEpisode2.Release.Size = 250.Megabytes();
remoteMovie2.Release.PublishDate = DateTime.UtcNow.AddDays(-150);
remoteMovie2.Release.Size = 250.Megabytes();
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Release.Should().Be(remoteEpisode1.Release);
qualifiedReports.First().RemoteMovie.Release.Should().Be(remoteMovie1.Release);
}
[Test]
public void should_prefer_more_prioritized_words()
{
var remoteEpisode1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteEpisode2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie1 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
var remoteMovie2 = GivenRemoteMovie(new QualityModel(Quality.HDTV720p));
remoteEpisode1.Release.Title += " DTS-HD";
remoteEpisode2.Release.Title += " DTS-HD SPARKS";
remoteMovie1.Release.Title += " DTS-HD";
remoteMovie2.Release.Title += " DTS-HD SPARKS";
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteEpisode1));
decisions.Add(new DownloadDecision(remoteEpisode2));
decisions.Add(new DownloadDecision(remoteMovie1));
decisions.Add(new DownloadDecision(remoteMovie2));
var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions);
qualifiedReports.First().RemoteMovie.Release.Should().Be(remoteEpisode2.Release);
qualifiedReports.First().RemoteMovie.Release.Should().Be(remoteMovie2.Release);
}
[Test]

View File

@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Data;
using Dapper;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(171)]
public class quality_definition_preferred_size : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("QualityDefinitions").AddColumn("PreferredSize").AsDouble().Nullable();
Execute.WithConnection(UpdateQualityDefinitions);
}
private void UpdateQualityDefinitions(IDbConnection conn, IDbTransaction tran)
{
var existing = conn.Query<QualityDefinition170>("SELECT Id, MaxSize FROM QualityDefinitions");
var updated = new List<QualityDefinition171>();
foreach (var row in existing)
{
var maxSize = row.MaxSize;
var preferredSize = maxSize;
if (maxSize.HasValue && maxSize.Value > 5)
{
preferredSize = maxSize.Value - 5;
}
updated.Add(new QualityDefinition171
{
Id = row.Id,
PreferredSize = preferredSize
});
}
var updateSql = "UPDATE QualityDefinitions SET PreferredSize = @PreferredSize WHERE Id = @Id";
conn.Execute(updateSql, updated, transaction: tran);
}
private class QualityDefinition170 : ModelBase
{
public int? MaxSize { get; set; }
}
private class QualityDefinition171 : ModelBase
{
public int? PreferredSize { get; set; }
}
}
}

View File

@ -5,6 +5,7 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine
{
@ -12,14 +13,16 @@ namespace NzbDrone.Core.DecisionEngine
{
private readonly IConfigService _configService;
private readonly IDelayProfileService _delayProfileService;
private readonly IQualityDefinitionService _qualityDefinitionService;
public delegate int CompareDelegate(DownloadDecision x, DownloadDecision y);
public delegate int CompareDelegate<TSubject, TValue>(DownloadDecision x, DownloadDecision y);
public DownloadDecisionComparer(IConfigService configService, IDelayProfileService delayProfileService)
public DownloadDecisionComparer(IConfigService configService, IDelayProfileService delayProfileService, IQualityDefinitionService qualityDefinitionService)
{
_configService = configService;
_delayProfileService = delayProfileService;
_qualityDefinitionService = qualityDefinitionService;
}
public int Compare(DownloadDecision x, DownloadDecision y)
@ -164,8 +167,25 @@ namespace NzbDrone.Core.DecisionEngine
private int CompareSize(DownloadDecision x, DownloadDecision y)
{
// TODO: Is smaller better? Smaller for usenet could mean no par2 files.
return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteEpisode => remoteEpisode.Release.Size.Round(200.Megabytes()));
var sizeCompare = CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie =>
{
var preferredSize = _qualityDefinitionService.Get(remoteMovie.ParsedMovieInfo.Quality.Quality).PreferredSize;
// If no value for preferred it means unlimited so fallback to sort largest is best
if (preferredSize.HasValue && remoteMovie.Movie.Runtime > 0)
{
var preferredMovieSize = remoteMovie.Movie.Runtime * preferredSize.Value.Megabytes();
// Calculate closest to the preferred size
return Math.Abs((remoteMovie.Release.Size - preferredMovieSize).Round(200.Megabytes())) * (-1);
}
else
{
return remoteMovie.Release.Size.Round(200.Megabytes());
}
});
return sizeCompare;
}
private int ScoreFlags(IndexerFlags flags)

View File

@ -3,6 +3,7 @@ using System.Linq;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine
{
@ -15,11 +16,13 @@ namespace NzbDrone.Core.DecisionEngine
{
private readonly IConfigService _configService;
private readonly IDelayProfileService _delayProfileService;
private readonly IQualityDefinitionService _qualityDefinitionService;
public DownloadDecisionPriorizationService(IConfigService configService, IDelayProfileService delayProfileService)
public DownloadDecisionPriorizationService(IConfigService configService, IDelayProfileService delayProfileService, IQualityDefinitionService qualityDefinitionService)
{
_configService = configService;
_delayProfileService = delayProfileService;
_qualityDefinitionService = qualityDefinitionService;
}
public List<DownloadDecision> PrioritizeDecisionsForMovies(List<DownloadDecision> decisions)
@ -27,7 +30,7 @@ namespace NzbDrone.Core.DecisionEngine
return decisions.Where(c => c.RemoteMovie.MappingResult == MappingResultType.Success || c.RemoteMovie.MappingResult == MappingResultType.SuccessLenientMapping)
.GroupBy(c => c.RemoteMovie.Movie.Id, (movieId, downloadDecisions) =>
{
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_configService, _delayProfileService));
return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_configService, _delayProfileService, _qualityDefinitionService));
})
.SelectMany(c => c)
.Union(decisions.Where(c => c.RemoteMovie.MappingResult != MappingResultType.Success || c.RemoteMovie.MappingResult != MappingResultType.SuccessLenientMapping))

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Datastore;
@ -167,41 +167,41 @@ namespace NzbDrone.Core.Qualities
#if !LIBRARY
DefaultQualityDefinitions = new HashSet<QualityDefinition>
{
new QualityDefinition(Quality.Unknown) { Weight = 1, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.WORKPRINT) { Weight = 2, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.CAM) { Weight = 3, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.TELESYNC) { Weight = 4, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.TELECINE) { Weight = 5, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.REGIONAL) { Weight = 6, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.DVDSCR) { Weight = 7, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.SDTV) { Weight = 8, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.DVD) { Weight = 9, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.DVDR) { Weight = 10, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.Unknown) { Weight = 1, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.WORKPRINT) { Weight = 2, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.CAM) { Weight = 3, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.TELESYNC) { Weight = 4, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.TELECINE) { Weight = 5, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.REGIONAL) { Weight = 6, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.DVDSCR) { Weight = 7, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.SDTV) { Weight = 8, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.DVD) { Weight = 9, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.DVDR) { Weight = 10, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.WEBDL480p) { Weight = 11, MinSize = 0, MaxSize = 100, GroupName = "WEB 480p" },
new QualityDefinition(Quality.WEBRip480p) { Weight = 11, MinSize = 0, MaxSize = 100, GroupName = "WEB 480p" },
new QualityDefinition(Quality.Bluray480p) { Weight = 12, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.Bluray576p) { Weight = 13, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.WEBDL480p) { Weight = 11, MinSize = 0, MaxSize = 100, PreferredSize = 95, GroupName = "WEB 480p" },
new QualityDefinition(Quality.WEBRip480p) { Weight = 11, MinSize = 0, MaxSize = 100, PreferredSize = 95, GroupName = "WEB 480p" },
new QualityDefinition(Quality.Bluray480p) { Weight = 12, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.Bluray576p) { Weight = 13, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.HDTV720p) { Weight = 14, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.WEBDL720p) { Weight = 15, MinSize = 0, MaxSize = 100, GroupName = "WEB 720p" },
new QualityDefinition(Quality.WEBRip720p) { Weight = 15, MinSize = 0, MaxSize = 100, GroupName = "WEB 720p" },
new QualityDefinition(Quality.Bluray720p) { Weight = 16, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.HDTV720p) { Weight = 14, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.WEBDL720p) { Weight = 15, MinSize = 0, MaxSize = 100, PreferredSize = 95, GroupName = "WEB 720p" },
new QualityDefinition(Quality.WEBRip720p) { Weight = 15, MinSize = 0, MaxSize = 100, PreferredSize = 95, GroupName = "WEB 720p" },
new QualityDefinition(Quality.Bluray720p) { Weight = 16, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.HDTV1080p) { Weight = 17, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.WEBDL1080p) { Weight = 18, MinSize = 0, MaxSize = 100, GroupName = "WEB 1080p" },
new QualityDefinition(Quality.WEBRip1080p) { Weight = 18, MinSize = 0, MaxSize = 100, GroupName = "WEB 1080p" },
new QualityDefinition(Quality.Bluray1080p) { Weight = 19, MinSize = 0, MaxSize = null },
new QualityDefinition(Quality.Remux1080p) { Weight = 20, MinSize = 0, MaxSize = null },
new QualityDefinition(Quality.HDTV1080p) { Weight = 17, MinSize = 0, MaxSize = 100, PreferredSize = 95 },
new QualityDefinition(Quality.WEBDL1080p) { Weight = 18, MinSize = 0, MaxSize = 100, PreferredSize = 95, GroupName = "WEB 1080p" },
new QualityDefinition(Quality.WEBRip1080p) { Weight = 18, MinSize = 0, MaxSize = 100, PreferredSize = 95, GroupName = "WEB 1080p" },
new QualityDefinition(Quality.Bluray1080p) { Weight = 19, MinSize = 0, MaxSize = null, PreferredSize = null },
new QualityDefinition(Quality.Remux1080p) { Weight = 20, MinSize = 0, MaxSize = null, PreferredSize = null },
new QualityDefinition(Quality.HDTV2160p) { Weight = 21, MinSize = 0, MaxSize = null },
new QualityDefinition(Quality.WEBDL2160p) { Weight = 22, MinSize = 0, MaxSize = null, GroupName = "WEB 2160p" },
new QualityDefinition(Quality.WEBRip2160p) { Weight = 22, MinSize = 0, MaxSize = null, GroupName = "WEB 2160p" },
new QualityDefinition(Quality.Bluray2160p) { Weight = 23, MinSize = 0, MaxSize = null },
new QualityDefinition(Quality.Remux2160p) { Weight = 24, MinSize = 0, MaxSize = null },
new QualityDefinition(Quality.HDTV2160p) { Weight = 21, MinSize = 0, MaxSize = null, PreferredSize = null },
new QualityDefinition(Quality.WEBDL2160p) { Weight = 22, MinSize = 0, MaxSize = null, PreferredSize = null, GroupName = "WEB 2160p" },
new QualityDefinition(Quality.WEBRip2160p) { Weight = 22, MinSize = 0, MaxSize = null, PreferredSize = null, GroupName = "WEB 2160p" },
new QualityDefinition(Quality.Bluray2160p) { Weight = 23, MinSize = 0, MaxSize = null, PreferredSize = null },
new QualityDefinition(Quality.Remux2160p) { Weight = 24, MinSize = 0, MaxSize = null, PreferredSize = null },
new QualityDefinition(Quality.BRDISK) { Weight = 25, MinSize = 0, MaxSize = null },
new QualityDefinition(Quality.RAWHD) { Weight = 26, MinSize = 0, MaxSize = null }
new QualityDefinition(Quality.BRDISK) { Weight = 25, MinSize = 0, MaxSize = null, PreferredSize = null },
new QualityDefinition(Quality.RAWHD) { Weight = 26, MinSize = 0, MaxSize = null, PreferredSize = null }
};
#endif
}

View File

@ -12,6 +12,7 @@ namespace NzbDrone.Core.Qualities
public double? MinSize { get; set; }
public double? MaxSize { get; set; }
public double? PreferredSize { get; set; }
public QualityDefinition()
{

View File

@ -15,6 +15,7 @@ namespace Radarr.Api.V3.Qualities
public double? MinSize { get; set; }
public double? MaxSize { get; set; }
public double? PreferredSize { get; set; }
}
public static class QualityDefinitionResourceMapper
@ -38,6 +39,7 @@ namespace Radarr.Api.V3.Qualities
MinSize = model.MinSize,
MaxSize = model.MaxSize,
PreferredSize = model.PreferredSize
};
}
@ -60,6 +62,7 @@ namespace Radarr.Api.V3.Qualities
MinSize = resource.MinSize,
MaxSize = resource.MaxSize,
PreferredSize = resource.PreferredSize
};
}

View File

@ -7250,10 +7250,10 @@ react-side-effect@^1.0.2:
dependencies:
shallowequal "^1.0.1"
react-slider@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/react-slider/-/react-slider-1.0.1.tgz#4cb212d31c35d804805e31f02ce37771e95d5d45"
integrity sha512-SI2anLzeKlFxnntoM93VXrf3ll9uL/TYjIJB6PsQVp4mDVt2VfWyGtMoUvK/ir/PnjuirNtF1pU3cG9XCezfBA==
react-slider@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/react-slider/-/react-slider-1.0.3.tgz#89bd9cf0bb87f07ce27d8724efa584d9c6e6dca8"
integrity sha512-6HSkzHeyPthoCuQseZ16KQePdj0VloHllXXXmtO1733wGhFa2v1kKjIuGpfHAgWshqB3F7Xn2s5FxTu3xvFXbg==
react-tabs@3.0.0:
version "3.0.0"