diff --git a/src/NzbDrone.Api/Qualities/QualityDefinitionResource.cs b/src/NzbDrone.Api/Qualities/QualityDefinitionResource.cs index 8750a6c33..7c65ed777 100644 --- a/src/NzbDrone.Api/Qualities/QualityDefinitionResource.cs +++ b/src/NzbDrone.Api/Qualities/QualityDefinitionResource.cs @@ -8,11 +8,11 @@ namespace NzbDrone.Api.Qualities { public Quality Quality { get; set; } - public String Title { get; set; } + public string Title { get; set; } - public Int32 Weight { get; set; } + public int Weight { get; set; } - public Int32 MinSize { get; set; } - public Int32 MaxSize { get; set; } + public double? MinSize { get; set; } + public double? MaxSize { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/084_update_quality_minmax_sizeFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/084_update_quality_minmax_sizeFixture.cs new file mode 100644 index 000000000..a5f628245 --- /dev/null +++ b/src/NzbDrone.Core.Test/Datastore/Migration/084_update_quality_minmax_sizeFixture.cs @@ -0,0 +1,103 @@ +using System; +using System.Linq; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Datastore.Migration +{ + [TestFixture] + public class update_quality_minmax_size : MigrationTest + { + [Test] + public void should_not_fail_if_empty() + { + WithTestDb(c => + { + + }); + + var items = Mocker.Resolve().All(); + + items.Should().HaveCount(0); + } + + [Test] + public void should_set_rawhd_to_null() + { + WithTestDb(c => + { + c.Insert.IntoTable("QualityDefinitions").Row(new + { + Quality = 1, + Title = "SDTV", + MinSize = 0, + MaxSize = 100 + }) + .Row(new + { + Quality = 10, + Title = "RawHD", + MinSize = 0, + MaxSize = 100 + }); + }); + + var items = Mocker.Resolve().All(); + + items.Should().HaveCount(2); + + items.First(v => v.Quality.Id == 10).MaxSize.Should().NotHaveValue(); + } + + [Test] + public void should_set_zero_maxsize_to_null() + { + WithTestDb(c => + { + c.Insert.IntoTable("QualityDefinitions").Row(new + { + Quality = 1, + Title = "SDTV", + MinSize = 0, + MaxSize = 0 + }); + }); + + var items = Mocker.Resolve().All(); + + items.Should().HaveCount(1); + + items.First(v => v.Quality.Id == 1).MaxSize.Should().NotHaveValue(); + } + + [Test] + public void should_preserve_values() + { + WithTestDb(c => + { + c.Insert.IntoTable("QualityDefinitions").Row(new + { + Quality = 1, + Title = "SDTV", + MinSize = 0, + MaxSize = 100 + }) + .Row(new + { + Quality = 10, + Title = "RawHD", + MinSize = 0, + MaxSize = 100 + }); + }); + + var items = Mocker.Resolve().All(); + + items.Should().HaveCount(2); + + items.First(v => v.Quality.Id == 1).MaxSize.Should().Be(100); + } + } +} diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs index 493ffa70c..d04742d9b 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/AcceptableSizeSpecificationFixture.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using Moq; @@ -6,8 +7,8 @@ using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; -using NzbDrone.Core.Tv; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Test.DecisionEngineTests { @@ -52,6 +53,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests }; + Mocker.GetMock() + .Setup(v => v.Get(It.IsAny())) + .Returns(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v)); + qualityType = Builder.CreateNew() .With(q => q.MinSize = 2) .With(q => q.MaxSize = 10) @@ -144,7 +149,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests series.Runtime = 30; parseResultSingle.Series = series; parseResultSingle.Release.Size = 18457280000; - qualityType.MaxSize = 0; + qualityType.MaxSize = null; Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue(); } @@ -157,9 +162,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests series.Runtime = 60; parseResultSingle.Series = series; parseResultSingle.Release.Size = 36857280000; - qualityType.MaxSize = 0; + qualityType.MaxSize = null; - Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue(); ; + Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue(); } [Test] @@ -180,12 +185,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_return_true_if_RAWHD() { - var parseResult = new RemoteEpisode - { - ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.RAWHD) }, - }; + parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.RAWHD); + + series.Runtime = 45; + parseResultSingle.Series = series; + parseResultSingle.Series.SeriesType = SeriesTypes.Daily; + parseResultSingle.Release.Size = 8000.Megabytes(); - Subject.IsSatisfiedBy(parseResult, null).Accepted.Should().BeTrue(); + Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue(); } [Test] diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 1328e6e35..f59fd6f4c 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -121,6 +121,7 @@ + diff --git a/src/NzbDrone.Core/Datastore/Converters/DoubleConverter.cs b/src/NzbDrone.Core/Datastore/Converters/DoubleConverter.cs new file mode 100644 index 000000000..af3603654 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Converters/DoubleConverter.cs @@ -0,0 +1,46 @@ +using System; +using Marr.Data.Converters; +using Marr.Data.Mapping; + +namespace NzbDrone.Core.Datastore.Converters +{ + public class DoubleConverter : IConverter + { + public object FromDB(ConverterContext context) + { + if (context.DbValue == DBNull.Value) + { + return DBNull.Value; + } + + if (context.DbValue is Double) + { + return context.DbValue; + } + + return Convert.ToDouble(context.DbValue); + } + + public object FromDB(ColumnMap map, object dbValue) + { + if (dbValue == DBNull.Value) + { + return DBNull.Value; + } + + if (dbValue is Double) + { + return dbValue; + } + + return Convert.ToDouble(dbValue); + } + + public object ToDB(object clrValue) + { + return clrValue; + } + + public Type DbType { get; private set; } + } +} diff --git a/src/NzbDrone.Core/Datastore/Migration/084_update_quality_minmax_size.cs b/src/NzbDrone.Core/Datastore/Migration/084_update_quality_minmax_size.cs new file mode 100644 index 000000000..6e9b68dfd --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/084_update_quality_minmax_size.cs @@ -0,0 +1,18 @@ +using System.Data; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(84)] + public class update_quality_minmax_size : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("QualityDefinitions").AlterColumn("MinSize").AsDouble().Nullable(); + Alter.Table("QualityDefinitions").AlterColumn("MaxSize").AsDouble().Nullable(); + + Execute.Sql("UPDATE QualityDefinitions SET MaxSize = NULL WHERE Quality = 10 OR MaxSize = 0"); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index d769a9a41..180486861 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -114,6 +114,7 @@ namespace NzbDrone.Core.Datastore RegisterProviderSettingConverter(); MapRepository.Instance.RegisterTypeConverter(typeof(Int32), new Int32Converter()); + MapRepository.Instance.RegisterTypeConverter(typeof(Double), new DoubleConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(DateTime), new UtcConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(Boolean), new BooleanIntConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(Enum), new EnumIntConverter()); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs index 28f1ca697..a9897e229 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/AcceptableSizeSpecification.cs @@ -30,12 +30,6 @@ namespace NzbDrone.Core.DecisionEngine.Specifications var quality = subject.ParsedEpisodeInfo.Quality.Quality; - if (quality == Quality.RAWHD) - { - _logger.Debug("Raw-HD release found, skipping size check."); - return Decision.Accept(); - } - if (subject.ParsedEpisodeInfo.Special) { _logger.Debug("Special release found, skipping size check."); @@ -43,24 +37,27 @@ namespace NzbDrone.Core.DecisionEngine.Specifications } var qualityDefinition = _qualityDefinitionService.Get(quality); - var minSize = qualityDefinition.MinSize.Megabytes(); + if (qualityDefinition.MinSize.HasValue) + { + var minSize = qualityDefinition.MinSize.Value.Megabytes(); - //Multiply maxSize by Series.Runtime - minSize = minSize * subject.Series.Runtime * subject.Episodes.Count; + //Multiply maxSize by Series.Runtime + minSize = minSize * subject.Series.Runtime * subject.Episodes.Count; - //If the parsed size is smaller than minSize we don't want it - if (subject.Release.Size < minSize) - { - _logger.Debug("Item: {0}, Size: {1:0n} is smaller than minimum allowed size ({2:0}), rejecting.", subject, subject.Release.Size, minSize); - return Decision.Reject("{0} is smaller than minimum allowed: {1}", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix()); + //If the parsed size is smaller than minSize we don't want it + if (subject.Release.Size < minSize) + { + _logger.Debug("Item: {0}, Size: {1:0n} is smaller than minimum allowed size ({2:0}), rejecting.", subject, subject.Release.Size, minSize); + return Decision.Reject("{0} is smaller than minimum allowed: {1}", subject.Release.Size.SizeSuffix(), minSize.SizeSuffix()); + } } - if (qualityDefinition.MaxSize == 0) + if (!qualityDefinition.MaxSize.HasValue) { - _logger.Debug("Max size is 0 (unlimited) - skipping check."); + _logger.Debug("Max size is unlimited - skipping check."); } else { - var maxSize = qualityDefinition.MaxSize.Megabytes(); + var maxSize = qualityDefinition.MaxSize.Value.Megabytes(); //Multiply maxSize by Series.Runtime maxSize = maxSize * subject.Series.Runtime * subject.Episodes.Count; diff --git a/src/NzbDrone.Core/Fluent.cs b/src/NzbDrone.Core/Fluent.cs index b7ae4824b..710eb7cf6 100644 --- a/src/NzbDrone.Core/Fluent.cs +++ b/src/NzbDrone.Core/Fluent.cs @@ -30,6 +30,16 @@ namespace NzbDrone.Core return Convert.ToInt64(gigabytes * 1024L * 1024L * 1024L); } + public static Int64 Megabytes(this double megabytes) + { + return Convert.ToInt64(megabytes * 1024L * 1024L); + } + + public static Int64 Gigabytes(this double gigabytes) + { + return Convert.ToInt64(gigabytes * 1024L * 1024L * 1024L); + } + public static Int64 Round(this long number, long level) { diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 497144d20..6ec14cdd4 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -151,6 +151,7 @@ + @@ -251,6 +252,7 @@ + diff --git a/src/NzbDrone.Core/Qualities/Quality.cs b/src/NzbDrone.Core/Qualities/Quality.cs index bc84f3805..9fba87e84 100644 --- a/src/NzbDrone.Core/Qualities/Quality.cs +++ b/src/NzbDrone.Core/Qualities/Quality.cs @@ -101,7 +101,7 @@ namespace NzbDrone.Core.Qualities new QualityDefinition(Quality.DVD) { Weight = 4, MinSize = 0, MaxSize = 100 }, new QualityDefinition(Quality.HDTV720p) { Weight = 5, MinSize = 0, MaxSize = 100 }, new QualityDefinition(Quality.HDTV1080p) { Weight = 6, MinSize = 0, MaxSize = 100 }, - new QualityDefinition(Quality.RAWHD) { Weight = 7, MinSize = 0, MaxSize = 100 }, + new QualityDefinition(Quality.RAWHD) { Weight = 7, MinSize = 0, MaxSize = null }, new QualityDefinition(Quality.WEBDL720p) { Weight = 8, MinSize = 0, MaxSize = 100 }, new QualityDefinition(Quality.Bluray720p) { Weight = 9, MinSize = 0, MaxSize = 100 }, new QualityDefinition(Quality.WEBDL1080p) { Weight = 10, MinSize = 0, MaxSize = 100 }, diff --git a/src/NzbDrone.Core/Qualities/QualityDefinition.cs b/src/NzbDrone.Core/Qualities/QualityDefinition.cs index 14cc1f008..372002333 100644 --- a/src/NzbDrone.Core/Qualities/QualityDefinition.cs +++ b/src/NzbDrone.Core/Qualities/QualityDefinition.cs @@ -11,8 +11,8 @@ namespace NzbDrone.Core.Qualities public int Weight { get; set; } - public int MinSize { get; set; } - public int MaxSize { get; set; } + public double? MinSize { get; set; } + public double? MaxSize { get; set; } public QualityDefinition() { diff --git a/src/UI/Settings/Quality/Definition/QualityDefinitionCollectionTemplate.hbs b/src/UI/Settings/Quality/Definition/QualityDefinitionCollectionTemplate.hbs index c1c556274..ac514ba90 100644 --- a/src/UI/Settings/Quality/Definition/QualityDefinitionCollectionTemplate.hbs +++ b/src/UI/Settings/Quality/Definition/QualityDefinitionCollectionTemplate.hbs @@ -6,7 +6,7 @@
Quality Title - Size Limit + Size Limit
diff --git a/src/UI/Settings/Quality/Definition/QualityDefinitionItemView.js b/src/UI/Settings/Quality/Definition/QualityDefinitionItemView.js index f2f19d7b6..d0b087d78 100644 --- a/src/UI/Settings/Quality/Definition/QualityDefinitionItemView.js +++ b/src/UI/Settings/Quality/Definition/QualityDefinitionItemView.js @@ -6,6 +6,12 @@ require('jquery-ui'); var view = Marionette.ItemView.extend({ template : 'Settings/Quality/Definition/QualityDefinitionItemViewTemplate', className : 'row', + + slider : { + min : 0, + max : 200, + step : 0.1 + }, ui : { sizeSlider : '.x-slider', @@ -31,11 +37,12 @@ var view = Marionette.ItemView.extend({ this.ui.sizeSlider.slider({ range : true, - min : 0, - max : 200, + min : this.slider.min, + max : this.slider.max, + step : this.slider.step, values : [ - this.model.get('minSize'), - this.model.get('maxSize') + this.model.get('minSize') || this.slider.min, + this.model.get('maxSize') || this.slider.max ] }); @@ -43,15 +50,22 @@ var view = Marionette.ItemView.extend({ }, _updateSize : function(event, ui) { - this.model.set('minSize', ui.values[0]); - this.model.set('maxSize', ui.values[1]); + var minSize = ui.values[0]; + var maxSize = ui.values[1]; + + if (maxSize === this.slider.max) { + maxSize = null; + } + + this.model.set('minSize', minSize); + this.model.set('maxSize', maxSize); this._changeSize(); }, _changeSize : function() { - var minSize = this.model.get('minSize'); - var maxSize = this.model.get('maxSize'); + var minSize = this.model.get('minSize') || this.slider.min; + var maxSize = this.model.get('maxSize') || null; { var minBytes = minSize * 1024 * 1024; @@ -63,24 +77,18 @@ var view = Marionette.ItemView.extend({ } { - if (maxSize === 0) { + if (maxSize === 0 || maxSize === null) { this.ui.thirtyMinuteMaxSize.html('Unlimited'); this.ui.sixtyMinuteMaxSize.html('Unlimited'); + } else { + var maxBytes = maxSize * 1024 * 1024; + var maxThirty = fileSize(maxBytes * 30, 1, false); + var maxSixty = fileSize(maxBytes * 60, 1, false); - return; + this.ui.thirtyMinuteMaxSize.html(maxThirty); + this.ui.sixtyMinuteMaxSize.html(maxSixty); } - - var maxBytes = maxSize * 1024 * 1024; - var maxThirty = fileSize(maxBytes * 30, 1, false); - var maxSixty = fileSize(maxBytes * 60, 1, false); - - this.ui.thirtyMinuteMaxSize.html(maxThirty); - this.ui.sixtyMinuteMaxSize.html(maxSixty); } - /*if (parseInt(maxSize, 10) === 0) { - thirty = 'No Limit'; - sixty = 'No Limit'; - }*/ } });