From 43c48bf83373b4fab1415347dad936759aee07b1 Mon Sep 17 00:00:00 2001 From: Qstick Date: Tue, 11 Feb 2020 20:31:44 -0500 Subject: [PATCH] New: Quality Preferred Setting --- .../Quality/Definition/QualityDefinition.css | 4 +- .../Quality/Definition/QualityDefinition.js | 83 +++++- .../Definition/QualityDefinitionConnector.js | 10 +- package.json | 2 +- .../PrioritizeDownloadDecisionFixture.cs | 246 ++++++++++++------ .../171_quality_definition_preferred_size.cs | 56 ++++ .../DownloadDecisionComparer.cs | 26 +- .../DownloadDecisionPriorizationService.cs | 7 +- src/NzbDrone.Core/Qualities/Quality.cs | 72 ++--- .../Qualities/QualityDefinition.cs | 1 + .../Qualities/QualityDefinitionResource.cs | 3 + yarn.lock | 8 +- 12 files changed, 375 insertions(+), 143 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/171_quality_definition_preferred_size.cs diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinition.css b/frontend/src/Settings/Quality/Definition/QualityDefinition.css index ef4deb180..e81febc1e 100644 --- a/frontend/src/Settings/Quality/Definition/QualityDefinition.css +++ b/frontend/src/Settings/Quality/Definition/QualityDefinition.css @@ -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 { diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinition.js b/frontend/src/Settings/Quality/Definition/QualityDefinition.js index 53e1718f1..172804947 100644 --- a/frontend/src/Settings/Quality/Definition/QualityDefinition.js +++ b/frontend/src/Settings/Quality/Definition/QualityDefinition.js @@ -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 { /> +
+ {preferredSixty} + } + title="Preferred Size" + body={ + + } + position={tooltipPositions.BOTTOM} + /> +
+
+
+ Preferred + + +
+
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 diff --git a/frontend/src/Settings/Quality/Definition/QualityDefinitionConnector.js b/frontend/src/Settings/Quality/Definition/QualityDefinitionConnector.js index a76c9440f..5f23d2765 100644 --- a/frontend/src/Settings/Quality/Definition/QualityDefinitionConnector.js +++ b/frontend/src/Settings/Quality/Definition/QualityDefinitionConnector.js @@ -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 }; diff --git a/package.json b/package.json index 390156e06..a67691435 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs index de120fd99..4ee35f7ab 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs @@ -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() + .Setup(s => s.Get(It.IsAny())) + .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() + .Setup(s => s.Get(It.IsAny())) + .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(); - 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(); - 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(); + 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(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(); - decisions.Add(new DownloadDecision(remoteEpisodeSd)); - decisions.Add(new DownloadDecision(remoteEpisodeHdSmallOld)); - decisions.Add(new DownloadDecision(remoteEpisodeSmallYoung)); - decisions.Add(new DownloadDecision(remoteEpisodeHdLargeYoung)); + decisions.Add(new DownloadDecision(remoteMovieSmall)); + decisions.Add(new DownloadDecision(remoteMovieLarge)); var qualifiedReports = Subject.PrioritizeDecisionsForMovies(decisions); - qualifiedReports.First().RemoteMovie.Should().Be(remoteEpisodeHdLargeYoung); + 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(); + 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(); + 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(); + 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(); - 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(); - 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(); - 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(); - 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(); - 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(); - 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(); - 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(); - 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(); - 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] diff --git a/src/NzbDrone.Core/Datastore/Migration/171_quality_definition_preferred_size.cs b/src/NzbDrone.Core/Datastore/Migration/171_quality_definition_preferred_size.cs new file mode 100644 index 000000000..f9ac4b7aa --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/171_quality_definition_preferred_size.cs @@ -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("SELECT Id, MaxSize FROM QualityDefinitions"); + + var updated = new List(); + + 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; } + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs index cb83fd9bc..653fc4c1b 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs @@ -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(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) diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs index 163ff7a9f..e84e20f51 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs @@ -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 PrioritizeDecisionsForMovies(List 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)) diff --git a/src/NzbDrone.Core/Qualities/Quality.cs b/src/NzbDrone.Core/Qualities/Quality.cs index 4a15f1bec..2bb812a9e 100644 --- a/src/NzbDrone.Core/Qualities/Quality.cs +++ b/src/NzbDrone.Core/Qualities/Quality.cs @@ -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 { - 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.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.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.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.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.BRDISK) { Weight = 25, MinSize = 0, MaxSize = null }, - new QualityDefinition(Quality.RAWHD) { Weight = 26, MinSize = 0, MaxSize = null } + 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, 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, 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, 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, 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, PreferredSize = null }, + new QualityDefinition(Quality.RAWHD) { Weight = 26, MinSize = 0, MaxSize = null, PreferredSize = null } }; #endif } diff --git a/src/NzbDrone.Core/Qualities/QualityDefinition.cs b/src/NzbDrone.Core/Qualities/QualityDefinition.cs index 74fb21793..ce6307eac 100644 --- a/src/NzbDrone.Core/Qualities/QualityDefinition.cs +++ b/src/NzbDrone.Core/Qualities/QualityDefinition.cs @@ -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() { diff --git a/src/Radarr.Api.V3/Qualities/QualityDefinitionResource.cs b/src/Radarr.Api.V3/Qualities/QualityDefinitionResource.cs index eb0c0ec18..1522fe563 100644 --- a/src/Radarr.Api.V3/Qualities/QualityDefinitionResource.cs +++ b/src/Radarr.Api.V3/Qualities/QualityDefinitionResource.cs @@ -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 }; } diff --git a/yarn.lock b/yarn.lock index 7e0f1afaf..347a65ce6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"