diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 38baf2798..41a19adcc 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -331,10 +331,10 @@ stages: artifactName: '$(testName)Tests' targetPath: $(testsFolder) - bash: | - wget https://mediaarea.net/repo/deb/repo-mediaarea_1.0-9_all.deb - sudo dpkg -i repo-mediaarea_1.0-9_all.deb + wget https://mediaarea.net/repo/deb/repo-mediaarea_1.0-11_all.deb + sudo dpkg -i repo-mediaarea_1.0-11_all.deb sudo apt-get update - sudo apt-get install -y libmediainfo-dev libmediainfo0v5 mediainfo + sudo apt-get install -y --allow-unauthenticated libmediainfo-dev libmediainfo0v5 mediainfo displayName: Install mediainfo condition: and(succeeded(), eq(variables['osName'], 'Linux')) - powershell: Set-Service SCardSvr -StartupType Manual diff --git a/frontend/src/Activity/Blacklist/BlacklistRow.js b/frontend/src/Activity/Blacklist/BlacklistRow.js index a7a66050f..3f715f2a7 100644 --- a/frontend/src/Activity/Blacklist/BlacklistRow.js +++ b/frontend/src/Activity/Blacklist/BlacklistRow.js @@ -44,6 +44,7 @@ class BlacklistRow extends Component { movie, sourceTitle, quality, + customFormats, languages, date, protocol, @@ -112,11 +113,11 @@ class BlacklistRow extends Component { ); } - if (name === 'quality.customFormats') { + if (name === 'customFormats') { return ( ); @@ -186,6 +187,7 @@ BlacklistRow.propTypes = { movie: PropTypes.object.isRequired, sourceTitle: PropTypes.string.isRequired, quality: PropTypes.object.isRequired, + customFormats: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired, date: PropTypes.string.isRequired, protocol: PropTypes.string.isRequired, diff --git a/frontend/src/Activity/History/HistoryRow.js b/frontend/src/Activity/History/HistoryRow.js index 8c796d8e1..46af6c6c3 100644 --- a/frontend/src/Activity/History/HistoryRow.js +++ b/frontend/src/Activity/History/HistoryRow.js @@ -54,6 +54,7 @@ class HistoryRow extends Component { const { movie, quality, + customFormats, languages, qualityCutoffNotMet, eventType, @@ -126,11 +127,11 @@ class HistoryRow extends Component { ); } - if (name === 'quality.customFormats') { + if (name === 'customFormats') { return ( ); @@ -219,6 +220,7 @@ HistoryRow.propTypes = { languages: PropTypes.arrayOf(PropTypes.object).isRequired, quality: PropTypes.object.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired, + customFormats: PropTypes.arrayOf(PropTypes.object).isRequired, eventType: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired, date: PropTypes.string.isRequired, diff --git a/frontend/src/Activity/Queue/QueueRow.js b/frontend/src/Activity/Queue/QueueRow.js index ff5f07899..5f9a1d810 100644 --- a/frontend/src/Activity/Queue/QueueRow.js +++ b/frontend/src/Activity/Queue/QueueRow.js @@ -72,6 +72,7 @@ class QueueRow extends Component { errorMessage, movie, quality, + customFormats, languages, protocol, indexer, @@ -169,11 +170,11 @@ class QueueRow extends Component { ); } - if (name === 'quality.customFormats') { + if (name === 'customFormats') { return ( ); @@ -329,6 +330,7 @@ QueueRow.propTypes = { errorMessage: PropTypes.string, movie: PropTypes.object, quality: PropTypes.object.isRequired, + customFormats: PropTypes.arrayOf(PropTypes.object), languages: PropTypes.arrayOf(PropTypes.object).isRequired, protocol: PropTypes.string.isRequired, indexer: PropTypes.string, diff --git a/frontend/src/InteractiveSearch/InteractiveSearchRow.js b/frontend/src/InteractiveSearch/InteractiveSearchRow.js index bb1a5f2c0..69d1c4ad2 100644 --- a/frontend/src/InteractiveSearch/InteractiveSearchRow.js +++ b/frontend/src/InteractiveSearch/InteractiveSearchRow.js @@ -113,6 +113,7 @@ class InteractiveSearchRow extends Component { seeders, leechers, quality, + customFormats, languages, indexerFlags, rejections, @@ -177,7 +178,7 @@ class InteractiveSearchRow extends Component { @@ -279,6 +280,7 @@ InteractiveSearchRow.propTypes = { seeders: PropTypes.number, leechers: PropTypes.number, quality: PropTypes.object.isRequired, + customFormats: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired, rejections: PropTypes.arrayOf(PropTypes.string).isRequired, indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired, diff --git a/frontend/src/Movie/History/MovieHistoryRow.js b/frontend/src/Movie/History/MovieHistoryRow.js index e21cc82dd..15241031e 100644 --- a/frontend/src/Movie/History/MovieHistoryRow.js +++ b/frontend/src/Movie/History/MovieHistoryRow.js @@ -9,6 +9,7 @@ import TableRow from 'Components/Table/TableRow'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import Popover from 'Components/Tooltip/Popover'; import MovieQuality from 'Movie/MovieQuality'; +import MovieFormats from 'Movie/MovieFormats'; import MovieLanguage from 'Movie/MovieLanguage'; import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector'; import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell'; @@ -64,6 +65,7 @@ class MovieHistoryRow extends Component { eventType, sourceTitle, quality, + customFormats, languages, qualityCutoffNotMet, date, @@ -98,6 +100,12 @@ class MovieHistoryRow extends Component { /> + + + + @@ -152,6 +160,7 @@ MovieHistoryRow.propTypes = { sourceTitle: PropTypes.string.isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired, quality: PropTypes.object.isRequired, + customFormats: PropTypes.arrayOf(PropTypes.object).isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired, date: PropTypes.string.isRequired, data: PropTypes.object.isRequired, diff --git a/frontend/src/Movie/History/MovieHistoryTableContent.js b/frontend/src/Movie/History/MovieHistoryTableContent.js index 2a914dcbf..c790233df 100644 --- a/frontend/src/Movie/History/MovieHistoryTableContent.js +++ b/frontend/src/Movie/History/MovieHistoryTableContent.js @@ -26,6 +26,12 @@ const columns = [ label: 'Quality', isVisible: true }, + { + name: 'customFormats', + label: 'Custom Formats', + isSortable: false, + isVisible: true + }, { name: 'date', label: 'Date', diff --git a/frontend/src/MovieFile/Editor/MovieFileEditorRow.js b/frontend/src/MovieFile/Editor/MovieFileEditorRow.js index 371419daa..bbc1cecab 100644 --- a/frontend/src/MovieFile/Editor/MovieFileEditorRow.js +++ b/frontend/src/MovieFile/Editor/MovieFileEditorRow.js @@ -86,6 +86,7 @@ class MovieFileEditorRow extends Component { size, quality, qualityCutoffNotMet, + customFormats, languages } = this.props; @@ -173,7 +174,7 @@ class MovieFileEditorRow extends Component { className={styles.formats} > @@ -233,6 +234,7 @@ MovieFileEditorRow.propTypes = { size: PropTypes.number.isRequired, relativePath: PropTypes.string.isRequired, quality: PropTypes.object.isRequired, + customFormats: PropTypes.arrayOf(PropTypes.object).isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired, mediaInfo: PropTypes.object.isRequired, diff --git a/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormat.js b/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormat.js index dacc0c019..974d4b5b5 100644 --- a/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormat.js +++ b/frontend/src/Settings/CustomFormats/CustomFormats/CustomFormat.js @@ -123,9 +123,6 @@ class CustomFormat extends Component {
Are you sure you want to delete custom format '{name}'?
-
- This will remove all associations to this format in the DB. This may result in existing files being updated. -
} confirmLabel="Delete" diff --git a/frontend/src/Store/Actions/Creators/createHandleActions.js b/frontend/src/Store/Actions/Creators/createHandleActions.js index 744186483..dc998b350 100644 --- a/frontend/src/Store/Actions/Creators/createHandleActions.js +++ b/frontend/src/Store/Actions/Creators/createHandleActions.js @@ -16,6 +16,13 @@ const blacklistedProperties = [ 'id' ]; +function createItemMap(data) { + return data.reduce((acc, d, index, array) => { + acc[d.id] = index; + return acc; + }, {}); +} + export default function createHandleActions(handlers, defaultState, section) { return handleActions({ @@ -42,7 +49,7 @@ export default function createHandleActions(handlers, defaultState, section) { if (_.isArray(payload.data)) { newState.items = payload.data; - newState.itemMap = _.zipObject(_.map(payload.data, 'id'), _.range(payload.data.length)); + newState.itemMap = createItemMap(newState.items); } else { newState.item = payload.data; } @@ -67,7 +74,7 @@ export default function createHandleActions(handlers, defaultState, section) { const items = newState.items; if (!newState.itemMap) { - newState.itemMap = _.zipObject(_.map(items, 'id'), _.range(items.length)); + newState.itemMap = createItemMap(items); } const index = payload.id in newState.itemMap ? newState.itemMap[payload.id] : -1; @@ -126,7 +133,7 @@ export default function createHandleActions(handlers, defaultState, section) { newState.items = [...newState.items]; _.remove(newState.items, { id: payload.id }); - newState.itemMap = _.zipObject(_.map(newState.items, 'id'), _.range(newState.items.length)); + newState.itemMap = createItemMap(newState.items); return updateSectionState(state, payloadSection, newState); } diff --git a/frontend/src/Store/Actions/blacklistActions.js b/frontend/src/Store/Actions/blacklistActions.js index e50433980..63737cc41 100644 --- a/frontend/src/Store/Actions/blacklistActions.js +++ b/frontend/src/Store/Actions/blacklistActions.js @@ -51,9 +51,9 @@ export const defaultState = { isVisible: true }, { - name: 'quality.customFormats', - label: 'Custom Formats', - isSortable: true, + name: 'customFormats', + label: 'Formats', + isSortable: false, isVisible: true }, { diff --git a/frontend/src/Store/Actions/historyActions.js b/frontend/src/Store/Actions/historyActions.js index a30de3716..54d1be04a 100644 --- a/frontend/src/Store/Actions/historyActions.js +++ b/frontend/src/Store/Actions/historyActions.js @@ -52,9 +52,9 @@ export const defaultState = { isVisible: true }, { - name: 'quality.customFormats', - label: 'Custom Formats', - isSortable: true, + name: 'customFormats', + label: 'Formats', + isSortable: false, isVisible: true }, { diff --git a/frontend/src/Store/Actions/queueActions.js b/frontend/src/Store/Actions/queueActions.js index 94145a187..1f568c14b 100644 --- a/frontend/src/Store/Actions/queueActions.js +++ b/frontend/src/Store/Actions/queueActions.js @@ -81,9 +81,9 @@ export const defaultState = { isVisible: true }, { - name: 'quality.customFormats', - label: 'Custom Formats', - isSortable: true, + name: 'customFormats', + label: 'Formats', + isSortable: false, isVisible: true }, { diff --git a/src/NzbDrone.Api/History/HistoryModule.cs b/src/NzbDrone.Api/History/HistoryModule.cs index 06b2106ae..63c534c65 100644 --- a/src/NzbDrone.Api/History/HistoryModule.cs +++ b/src/NzbDrone.Api/History/HistoryModule.cs @@ -35,7 +35,7 @@ namespace NzbDrone.Api.History if (model.Movie != null) { - resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(model.Movie.Profile, model.Quality); + resource.QualityCutoffNotMet = _qualityUpgradableSpecification.QualityCutoffNotMet(model.Movie.Profile, model.Quality); } return resource; diff --git a/src/NzbDrone.Api/Qualities/CustomFormatModule.cs b/src/NzbDrone.Api/Qualities/CustomFormatModule.cs index c14bfa4a2..94d9b6edc 100644 --- a/src/NzbDrone.Api/Qualities/CustomFormatModule.cs +++ b/src/NzbDrone.Api/Qualities/CustomFormatModule.cs @@ -11,11 +11,15 @@ namespace NzbDrone.Api.Qualities public class CustomFormatModule : RadarrRestModule { private readonly ICustomFormatService _formatService; + private readonly ICustomFormatCalculationService _formatCalculator; private readonly IParsingService _parsingService; - public CustomFormatModule(ICustomFormatService formatService, IParsingService parsingService) + public CustomFormatModule(ICustomFormatService formatService, + ICustomFormatCalculationService formatCalculator, + IParsingService parsingService) { _formatService = formatService; + _formatCalculator = formatCalculator; _parsingService = parsingService; SharedValidator.RuleFor(c => c.Name).NotEmpty(); @@ -103,8 +107,8 @@ namespace NzbDrone.Api.Qualities return new CustomFormatTestResource { - Matches = _parsingService.MatchFormatTags(parsed).ToResource(), - MatchedFormats = parsed.Quality.CustomFormats.ToResource() + Matches = _formatCalculator.MatchFormatTags(parsed).ToResource(), + MatchedFormats = _formatCalculator.ParseCustomFormat(parsed).ToResource() }; } @@ -125,8 +129,8 @@ namespace NzbDrone.Api.Qualities return new CustomFormatTestResource { - Matches = _parsingService.MatchFormatTags(parsed).ToResource(), - MatchedFormats = parsed.Quality.CustomFormats.ToResource() + Matches = _formatCalculator.MatchFormatTags(parsed).ToResource(), + MatchedFormats = _formatCalculator.ParseCustomFormat(parsed).ToResource() }; } } diff --git a/src/NzbDrone.Api/Qualities/FormatTagMatchResultResource.cs b/src/NzbDrone.Api/Qualities/FormatTagMatchResultResource.cs index c8209c2b3..fc8a03945 100644 --- a/src/NzbDrone.Api/Qualities/FormatTagMatchResultResource.cs +++ b/src/NzbDrone.Api/Qualities/FormatTagMatchResultResource.cs @@ -27,7 +27,7 @@ namespace NzbDrone.Api.Qualities public static class QualityTagMatchResultResourceMapper { - public static FormatTagMatchResultResource ToResource(this FormatTagMatchResult model) + public static FormatTagMatchResultResource ToResource(this CustomFormatMatchResult model) { if (model == null) { @@ -41,7 +41,7 @@ namespace NzbDrone.Api.Qualities }; } - public static List ToResource(this IList models) + public static List ToResource(this IList models) { return models.Select(ToResource).ToList(); } diff --git a/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs b/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs index 1b58b79c7..830e2af44 100644 --- a/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs +++ b/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs @@ -1,9 +1,7 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using FluentAssertions; using NUnit.Framework; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Events; @@ -22,11 +20,6 @@ namespace NzbDrone.Common.Test container.Register(new MainDatabase(null)); container.Resolve().Register(); - // A dummy custom format repository since this isn't a DB test - var mockCustomFormat = Mocker.GetMock(); - mockCustomFormat.Setup(x => x.All()).Returns(new List()); - container.Register(mockCustomFormat.Object); - Mocker.SetConstant(container); var handlers = Subject.BuildAll>() diff --git a/src/NzbDrone.Core.Test/CustomFormats/CustomFormatComparerFixture.cs b/src/NzbDrone.Core.Test/CustomFormats/CustomFormatComparerFixture.cs new file mode 100644 index 000000000..f994e1a9f --- /dev/null +++ b/src/NzbDrone.Core.Test/CustomFormats/CustomFormatComparerFixture.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Test.CustomFormats; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Qualities +{ + [TestFixture] + public class CustomFormatsComparerFixture : CoreTest + { + private CustomFormat _customFormat1; + private CustomFormat _customFormat2; + private CustomFormat _customFormat3; + private CustomFormat _customFormat4; + + public CustomFormatsComparer Subject { get; set; } + + [SetUp] + public void Setup() + { + } + + private void GivenDefaultProfileWithFormats() + { + _customFormat1 = new CustomFormat("My Format 1", "L_ENGLISH") { Id = 1 }; + _customFormat2 = new CustomFormat("My Format 2", "L_FRENCH") { Id = 2 }; + _customFormat3 = new CustomFormat("My Format 3", "L_SPANISH") { Id = 3 }; + _customFormat4 = new CustomFormat("My Format 4", "L_ITALIAN") { Id = 4 }; + + CustomFormatsFixture.GivenCustomFormats(CustomFormat.None, _customFormat1, _customFormat2, _customFormat3, _customFormat4); + + Subject = new CustomFormatsComparer(new Profile { Items = QualityFixture.GetDefaultQualities(), FormatItems = CustomFormatsFixture.GetSampleFormatItems() }); + } + + [Test] + public void should_be_lesser_when_first_format_is_worse() + { + GivenDefaultProfileWithFormats(); + + var first = new List { _customFormat1 }; + var second = new List { _customFormat2 }; + + var compare = Subject.Compare(first, second); + + compare.Should().BeLessThan(0); + } + + [Test] + public void should_be_zero_when_formats_are_equal() + { + GivenDefaultProfileWithFormats(); + + var first = new List { _customFormat2 }; + var second = new List { _customFormat2 }; + + var compare = Subject.Compare(first, second); + + compare.Should().Be(0); + } + + [Test] + public void should_be_greater_when_first_format_is_better() + { + GivenDefaultProfileWithFormats(); + + var first = new List { _customFormat3 }; + var second = new List { _customFormat2 }; + + var compare = Subject.Compare(first, second); + + compare.Should().BeGreaterThan(0); + } + + [Test] + public void should_be_greater_when_multiple_formats_better() + { + GivenDefaultProfileWithFormats(); + + var first = new List { _customFormat3, _customFormat4 }; + var second = new List { _customFormat2 }; + + var compare = Subject.Compare(first, second); + + compare.Should().BeGreaterThan(0); + } + + [Test] + public void should_be_greater_when_best_format_is_better() + { + GivenDefaultProfileWithFormats(); + + var first = new List { _customFormat1, _customFormat3 }; + var second = new List { _customFormat2 }; + + var compare = Subject.Compare(first, second); + + compare.Should().BeGreaterThan(0); + } + + [Test] + public void should_be_greater_when_best_format_equal_but_more_lower_formats() + { + GivenDefaultProfileWithFormats(); + + var first = new List { _customFormat1, _customFormat2 }; + var second = new List { _customFormat2 }; + + var compare = Subject.Compare(first, second); + + compare.Should().BeGreaterThan(0); + } + + [Test] + public void should_not_be_greater_when_best_format_worse_but_more_lower_formats() + { + GivenDefaultProfileWithFormats(); + + var first = new List { _customFormat1, _customFormat2, _customFormat3 }; + var second = new List { _customFormat4 }; + + var compare = Subject.Compare(first, second); + + compare.Should().BeLessThan(0); + } + } +} diff --git a/src/NzbDrone.Core.Test/CustomFormats/QualityTagFixture.cs b/src/NzbDrone.Core.Test/CustomFormats/QualityTagFixture.cs index 5daa39746..368d88b15 100644 --- a/src/NzbDrone.Core.Test/CustomFormats/QualityTagFixture.cs +++ b/src/NzbDrone.Core.Test/CustomFormats/QualityTagFixture.cs @@ -3,6 +3,7 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Parser; +using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.CustomFormats diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/165_remove_custom_formats_from_quality_model.cs b/src/NzbDrone.Core.Test/Datastore/Migration/165_remove_custom_formats_from_quality_model.cs new file mode 100644 index 000000000..88d9eb7de --- /dev/null +++ b/src/NzbDrone.Core.Test/Datastore/Migration/165_remove_custom_formats_from_quality_model.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Dapper; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Datastore.Migration; +using NzbDrone.Core.Download.Pending; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Datastore.Migration +{ + [TestFixture] + public class remove_custom_formats_from_quality_modelFixture : MigrationTest + { + [Test] + public void should_remove_custom_format_from_pending_releases() + { + var db = WithDapperMigrationTestDb(c => + { + c.Insert.IntoTable("PendingReleases").Row(new + { + MovieId = 1, + Title = "Test Movie", + Added = DateTime.UtcNow, + ParsedMovieInfo = @"{ + ""movieTitle"": ""Skyfall"", + ""simpleReleaseTitle"": ""A Movie (2012) \u002B Extras (1080p BluRay x265 HEVC 10bit DTS 5.1 SAMPA) [QxR]"", + ""quality"": { + ""quality"": { + ""id"": 7, + ""name"": ""Bluray-1080p"", + ""source"": ""bluray"", + ""resolution"": 1080, + ""modifier"": ""none"" + }, + ""customFormats"": [ + { + ""name"": ""Standard High Def Surround Sound Movie"", + ""formatTags"": [ + { + ""raw"": ""R_1080"", + ""tagType"": ""resolution"", + ""tagModifier"": 0, + ""value"": ""r1080p"" + }, + { + ""raw"": ""L_English"", + ""tagType"": ""language"", + ""tagModifier"": 0, + ""value"": { + ""id"": 1, + ""name"": ""English"" + } + }, + { + ""raw"": ""C_DTS"", + ""tagType"": ""custom"", + ""tagModifier"": 0, + ""value"": ""dts"" + } + ], + ""id"": 1 + } + ], + ""revision"": { + ""version"": 1, + ""real"": 0, + ""isRepack"": false + }, + ""hardcodedSubs"": null, + ""qualityDetectionSource"": ""name"" + }, + ""releaseGroup"": ""QxR"", + ""releaseHash"": """", + ""edition"": """", + ""year"": 2012, + ""imdbId"": """" +}", + Release = "{}", + Reason = PendingReleaseReason.Delay + }); + }); + + var json = db.Query("SELECT ParsedMovieInfo FROM PendingReleases").First(); + json.Should().NotContain("customFormats"); + + var pending = db.Query("SELECT ParsedMovieInfo FROM PendingReleases").First(); + pending.Quality.Quality.Should().Be(Quality.Bluray1080p); + pending.Languages.Should().BeEmpty(); + } + + [Test] + public void should_fix_quality_for_pending_releases() + { + var db = WithDapperMigrationTestDb(c => + { + c.Insert.IntoTable("PendingReleases").Row(new + { + MovieId = 1, + Title = "Test Movie", + Added = DateTime.UtcNow, + ParsedMovieInfo = @"{ + ""languages"": [ + ""english"" + ], + ""movieTitle"": ""Joy"", + ""simpleReleaseTitle"": ""A Movie.2015.1080p.BluRay.AVC.DTS-HD.MA.5.1-RARBG [f"", + ""quality"": { + ""quality"": { + ""id"": 7, + ""name"": ""Bluray-1080p"", + ""source"": ""bluray"", + ""resolution"": ""r1080P"", + ""modifier"": ""none"" + }, + ""customFormats"": [], + ""revision"": { + ""version"": 1, + ""real"": 0 + } + }, + ""releaseGroup"": ""RARBG"", + ""edition"": """", + ""year"": 2015, + ""imdbId"": """" +}", + Release = "{}", + Reason = PendingReleaseReason.Delay + }); + }); + + var json = db.Query("SELECT ParsedMovieInfo FROM PendingReleases").First(); + json.Should().NotContain("customFormats"); + json.Should().NotContain("resolution"); + + var pending = db.Query("SELECT ParsedMovieInfo FROM PendingReleases").First(); + pending.Quality.Quality.Should().Be(Quality.Bluray1080p); + pending.Languages.Should().BeEquivalentTo(new List { Language.English }); + } + } +} diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/CustomFormatAllowedByProfileSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/CustomFormatAllowedByProfileSpecificationFixture.cs index 78d259595..85add58f4 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/CustomFormatAllowedByProfileSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/CustomFormatAllowedByProfileSpecificationFixture.cs @@ -47,7 +47,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_allow_if_format_is_defined_in_profile() { - _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { _format1 }; + _remoteMovie.CustomFormats = new List { _format1 }; _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); @@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_deny_if_format_is_defined_in_profile() { - _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { _format2 }; + _remoteMovie.CustomFormats = new List { _format2 }; _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); @@ -65,7 +65,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_deny_if_one_format_is_defined_in_profile() { - _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { _format2, _format1 }; + _remoteMovie.CustomFormats = new List { _format2, _format1 }; _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); @@ -74,7 +74,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_allow_if_all_format_is_defined_in_profile() { - _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { _format2, _format1 }; + _remoteMovie.CustomFormats = new List { _format2, _format1 }; _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); @@ -83,7 +83,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_deny_if_no_format_was_parsed_and_none_not_in_profile() { - _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { }; + _remoteMovie.CustomFormats = new List { }; _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); @@ -92,7 +92,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_allow_if_no_format_was_parsed_and_none_in_profile() { - _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { }; + _remoteMovie.CustomFormats = new List { }; _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(CustomFormat.None.Name, _format1.Name, _format2.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs index a64347692..721d7fa91 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs @@ -1,8 +1,15 @@ +using System; using System.Collections.Generic; +using FizzWare.NBuilder; using FluentAssertions; +using Moq; using NUnit.Framework; +using NzbDrone.Common.Serializer; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.DecisionEngine.Specifications; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.CustomFormats; @@ -11,13 +18,55 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.DecisionEngineTests { [TestFixture] - public class CutoffSpecificationFixture : CoreTest + public class CutoffSpecificationFixture : CoreTest { private CustomFormat _customFormat; + private RemoteMovie _remoteMovie; [SetUp] public void Setup() { + Mocker.SetConstant(Mocker.Resolve()); + + _remoteMovie = new RemoteMovie() + { + Movie = Builder.CreateNew().Build(), + ParsedMovieInfo = Builder.CreateNew().With(x => x.Quality = null).Build() + }; + + GivenOldCustomFormats(new List()); + } + + private void GivenProfile(Profile profile) + { + CustomFormatsFixture.GivenCustomFormats(CustomFormat.None); + profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"); + profile.FormatCutoff = CustomFormat.None.Id; + _remoteMovie.Movie.Profile = profile; + + Console.WriteLine(profile.ToJson()); + } + + private void GivenFileQuality(QualityModel quality) + { + _remoteMovie.Movie.MovieFile = Builder.CreateNew().With(x => x.Quality = quality).Build(); + } + + private void GivenNewQuality(QualityModel quality) + { + _remoteMovie.ParsedMovieInfo.Quality = quality; + } + + private void GivenOldCustomFormats(List formats) + { + Mocker.GetMock() + .Setup(x => x.ParseCustomFormat(It.IsAny())) + .Returns(formats); + } + + private void GivenNewCustomFormats(List formats) + { + _remoteMovie.CustomFormats = formats; } private void GivenCustomFormatHigher() @@ -30,73 +79,80 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_return_true_if_current_episode_is_less_than_cutoff() { - Subject.CutoffNotMet(new Profile { Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }, - new QualityModel(Quality.DVD, new Revision(version: 2))).Should().BeTrue(); + GivenProfile(new Profile { Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }); + GivenFileQuality(new QualityModel(Quality.DVD, new Revision(version: 2))); + Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); } [Test] public void should_return_false_if_current_episode_is_equal_to_cutoff() { - Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }, - new QualityModel(Quality.HDTV720p, new Revision(version: 2))).Should().BeFalse(); + GivenProfile(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }); + GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); + Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); } [Test] public void should_return_false_if_current_episode_is_greater_than_cutoff() { - Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }, - new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); + GivenProfile(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }); + GivenFileQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); + Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); } [Test] public void should_return_true_when_new_episode_is_proper_but_existing_is_not() { - Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }, - new QualityModel(Quality.HDTV720p, new Revision(version: 1)), - new QualityModel(Quality.HDTV720p, new Revision(version: 2))).Should().BeTrue(); + GivenProfile(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }); + GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 1))); + GivenNewQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); + Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); } [Test] public void should_return_false_if_cutoff_is_met_and_quality_is_higher() { - Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }, - new QualityModel(Quality.HDTV720p, new Revision(version: 2)), - new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); + GivenProfile(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }); + GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); + GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); + Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); } [Test] public void should_return_false_if_custom_formats_is_met_and_quality_and_format_higher() { + GivenProfile(new Profile + { + Cutoff = Quality.HDTV720p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + FormatCutoff = CustomFormat.None.Id, + FormatItems = CustomFormatsFixture.GetSampleFormatItems("None", "My Format") + }); + + GivenFileQuality(new QualityModel(Quality.HDTV720p)); + GivenNewQuality(new QualityModel(Quality.Bluray1080p)); + GivenCustomFormatHigher(); - var old = new QualityModel(Quality.HDTV720p); - old.CustomFormats = new List { CustomFormat.None }; - var newQ = new QualityModel(Quality.Bluray1080p); - newQ.CustomFormats = new List { _customFormat }; - Subject.CutoffNotMet( - new Profile - { - Cutoff = Quality.HDTV720p.Id, - Items = Qualities.QualityFixture.GetDefaultQualities(), - FormatCutoff = CustomFormat.None.Id, - FormatItems = CustomFormatsFixture.GetSampleFormatItems("None", "My Format") - }, - old, - newQ).Should().BeFalse(); + + GivenOldCustomFormats(new List { CustomFormat.None }); + GivenNewCustomFormats(new List { _customFormat }); + + Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); } [Test] public void should_return_true_if_cutoffs_are_met_but_is_a_revision_upgrade() { - Profile profile = new Profile + GivenProfile(new Profile { Cutoff = Quality.HDTV1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities(), - }; + }); + + GivenFileQuality(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1))); + GivenNewQuality(new QualityModel(Quality.WEBDL1080p, new Revision(version: 2))); - Subject.CutoffNotMet( - profile, - new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)), - new QualityModel(Quality.WEBDL1080p, new Revision(version: 2))).Should().BeTrue(); + Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); } } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs index 128e42b76..517fa6a31 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/HistorySpecificationFixture.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Core.Configuration; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications.RssSync; using NzbDrone.Core.History; @@ -12,6 +14,7 @@ using NzbDrone.Core.Movies; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.CustomFormats; using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.DecisionEngineTests @@ -35,14 +38,23 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Mocker.Resolve(); _upgradeHistory = Mocker.Resolve(); + CustomFormatsFixture.GivenCustomFormats(CustomFormat.None); + _fakeMovie = Builder.CreateNew() - .With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }) - .Build(); + .With(c => c.Profile = new Profile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + Cutoff = Quality.Bluray1080p.Id, + FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"), + FormatCutoff = CustomFormat.None.Id + }) + .Build(); _parseResultSingle = new RemoteMovie { Movie = _fakeMovie, - ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) } + ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) }, + CustomFormats = new List() }; _upgradableQuality = new QualityModel(Quality.SDTV, new Revision(version: 1)); @@ -51,6 +63,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Mocker.GetMock() .SetupGet(s => s.EnableCompletedDownloadHandling) .Returns(true); + + Mocker.GetMock() + .Setup(x => x.ParseCustomFormat(It.IsAny())) + .Returns(new List()); } private void GivenMostRecentForEpisode(int episodeId, string downloadId, QualityModel quality, DateTime date, HistoryEventType eventType) @@ -142,10 +158,21 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_not_be_upgradable_if_episode_is_of_same_quality_as_existing() { - _fakeMovie.Profile = new Profile { Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }; + _fakeMovie.Profile = new Profile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + Cutoff = Quality.Bluray1080p.Id, + FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"), + FormatCutoff = CustomFormat.None.Id + }; + _parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); _upgradableQuality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); + Mocker.GetMock() + .Setup(x => x.ParseCustomFormat(It.IsAny())) + .Returns(new List()); + GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed); _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); @@ -154,7 +181,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_not_be_upgradable_if_cutoff_already_met() { - _fakeMovie.Profile = new Profile { Cutoff = Quality.WEBDL1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }; + _fakeMovie.Profile = new Profile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + Cutoff = Quality.WEBDL1080p.Id, + FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"), + FormatCutoff = CustomFormat.None.Id + }; + _parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); _upgradableQuality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)); @@ -182,7 +216,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_return_false_if_cutoff_already_met_and_cdh_is_disabled() { GivenCdhDisabled(); - _fakeMovie.Profile = new Profile { Cutoff = Quality.WEBDL1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }; + _fakeMovie.Profile = new Profile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + Cutoff = Quality.WEBDL1080p.Id, + FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"), + FormatCutoff = CustomFormat.None.Id + }; + _parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)); _upgradableQuality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs index 9eb60553e..c64a2602a 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs @@ -60,6 +60,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests remoteMovie.Release.DownloadProtocol = downloadProtocol; remoteMovie.Release.Title = "A Movie 1998"; + remoteMovie.CustomFormats = new List(); + return remoteMovie; } @@ -324,12 +326,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_prefer_better_custom_format() { var quality1 = new QualityModel(Quality.Bluray720p); - quality1.CustomFormats.Add(CustomFormat.None); var remoteMovie1 = GivenRemoteMovie(quality1); + remoteMovie1.CustomFormats.Add(CustomFormat.None); var quality2 = new QualityModel(Quality.Bluray720p); - quality2.CustomFormats.Add(_customFormat1); var remoteMovie2 = GivenRemoteMovie(quality2); + remoteMovie2.CustomFormats.Add(_customFormat1); var decisions = new List(); decisions.Add(new DownloadDecision(remoteMovie1)); @@ -343,12 +345,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_prefer_better_custom_format2() { var quality1 = new QualityModel(Quality.Bluray720p); - quality1.CustomFormats.Add(_customFormat1); var remoteMovie1 = GivenRemoteMovie(quality1); + remoteMovie1.CustomFormats.Add(_customFormat1); var quality2 = new QualityModel(Quality.Bluray720p); - quality2.CustomFormats.Add(_customFormat2); var remoteMovie2 = GivenRemoteMovie(quality2); + remoteMovie2.CustomFormats.Add(_customFormat2); var decisions = new List(); decisions.Add(new DownloadDecision(remoteMovie1)); @@ -362,12 +364,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public void should_prefer_2_custom_formats() { var quality1 = new QualityModel(Quality.Bluray720p); - quality1.CustomFormats.Add(_customFormat1); var remoteMovie1 = GivenRemoteMovie(quality1); + remoteMovie1.CustomFormats.Add(_customFormat1); var quality2 = new QualityModel(Quality.Bluray720p); - quality2.CustomFormats.AddRange(new List { _customFormat1, _customFormat2 }); var remoteMovie2 = GivenRemoteMovie(quality2); + remoteMovie2.CustomFormats.AddRange(new List { _customFormat1, _customFormat2 }); var decisions = new List(); decisions.Add(new DownloadDecision(remoteMovie1)); diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs index 907595566..bdacb0fec 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs @@ -1,9 +1,12 @@ -using FluentAssertions; +using System.Collections.Generic; +using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Configuration; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.CustomFormats; using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.DecisionEngineTests @@ -12,20 +15,39 @@ namespace NzbDrone.Core.Test.DecisionEngineTests public class QualityUpgradeSpecificationFixture : CoreTest { + private static CustomFormat _customFormat1 = new CustomFormat("My Format 1", "L_ENGLISH") { Id = 1 }; + private static CustomFormat _customFormat2 = new CustomFormat("My Format 2", "L_FRENCH") { Id = 2 }; + public static object[] IsUpgradeTestCases = { - new object[] { Quality.SDTV, 1, Quality.SDTV, 2, Quality.SDTV, true }, - new object[] { Quality.WEBDL720p, 1, Quality.WEBDL720p, 2, Quality.WEBDL720p, true }, - new object[] { Quality.SDTV, 1, Quality.SDTV, 1, Quality.SDTV, false }, - new object[] { Quality.WEBDL720p, 1, Quality.HDTV720p, 2, Quality.Bluray720p, false }, - new object[] { Quality.WEBDL720p, 1, Quality.HDTV720p, 2, Quality.WEBDL720p, false }, - new object[] { Quality.WEBDL720p, 1, Quality.WEBDL720p, 1, Quality.WEBDL720p, false }, - new object[] { Quality.WEBDL1080p, 1, Quality.WEBDL1080p, 1, Quality.WEBDL1080p, false } + // Quality upgrade trumps custom format + new object[] { Quality.SDTV, 1, new List(), Quality.SDTV, 2, new List(), true }, + new object[] { Quality.SDTV, 1, new List { _customFormat1 }, Quality.SDTV, 2, new List { _customFormat1 }, true }, + new object[] { Quality.SDTV, 1, new List { _customFormat1 }, Quality.SDTV, 2, new List { _customFormat2 }, true }, + new object[] { Quality.SDTV, 1, new List { _customFormat2 }, Quality.SDTV, 2, new List { _customFormat1 }, true }, + + // Revision upgrade trumps custom format + new object[] { Quality.WEBDL720p, 1, new List(), Quality.WEBDL720p, 2, new List(), true }, + new object[] { Quality.WEBDL720p, 1, new List { _customFormat1 }, Quality.WEBDL720p, 2, new List { _customFormat1 }, true }, + new object[] { Quality.WEBDL720p, 1, new List { _customFormat1 }, Quality.WEBDL720p, 2, new List { _customFormat2 }, true }, + new object[] { Quality.WEBDL720p, 1, new List { _customFormat2 }, Quality.WEBDL720p, 2, new List { _customFormat1 }, true }, + + // Custom formats apply if quality same + new object[] { Quality.SDTV, 1, new List(), Quality.SDTV, 1, new List(), false }, + new object[] { Quality.SDTV, 1, new List { _customFormat1 }, Quality.SDTV, 1, new List { _customFormat1 }, false }, + new object[] { Quality.SDTV, 1, new List { _customFormat1 }, Quality.SDTV, 1, new List { _customFormat2 }, true }, + new object[] { Quality.SDTV, 1, new List { _customFormat2 }, Quality.SDTV, 1, new List { _customFormat1 }, false }, + + new object[] { Quality.WEBDL720p, 1, new List(), Quality.HDTV720p, 2, new List(), false }, + new object[] { Quality.WEBDL720p, 1, new List(), Quality.HDTV720p, 2, new List(), false }, + new object[] { Quality.WEBDL720p, 1, new List(), Quality.WEBDL720p, 1, new List(), false }, + new object[] { Quality.WEBDL1080p, 1, new List(), Quality.WEBDL1080p, 1, new List(), false } }; [SetUp] public void Setup() { + CustomFormatsFixture.GivenCustomFormats(CustomFormat.None, _customFormat1, _customFormat2); } private void GivenAutoDownloadPropers(bool autoDownloadPropers) @@ -37,13 +59,27 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] [TestCaseSource("IsUpgradeTestCases")] - public void IsUpgradeTest(Quality current, int currentVersion, Quality newQuality, int newVersion, Quality cutoff, bool expected) + public void IsUpgradeTest(Quality current, + int currentVersion, + List currentFormats, + Quality newQuality, + int newVersion, + List newFormats, + bool expected) { GivenAutoDownloadPropers(true); - var profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }; + var profile = new Profile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + FormatItems = CustomFormatsFixture.GetSampleFormatItems() + }; - Subject.IsUpgradable(profile, new QualityModel(current, new Revision(version: currentVersion)), new QualityModel(newQuality, new Revision(version: newVersion))) + Subject.IsUpgradable(profile, + new QualityModel(current, new Revision(version: currentVersion)), + currentFormats, + new QualityModel(newQuality, new Revision(version: newVersion)), + newFormats) .Should().Be(expected); } @@ -54,7 +90,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests var profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }; - Subject.IsUpgradable(profile, new QualityModel(Quality.DVD, new Revision(version: 2)), new QualityModel(Quality.DVD, new Revision(version: 1))) + Subject.IsUpgradable(profile, + new QualityModel(Quality.DVD, new Revision(version: 2)), + new List(), + new QualityModel(Quality.DVD, new Revision(version: 1)), + new List()) .Should().BeFalse(); } @@ -69,7 +109,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Subject.IsUpgradable( profile, new QualityModel(Quality.HDTV720p, new Revision(version: 1)), - new QualityModel(Quality.HDTV720p, new Revision(version: 1))) + new List(), + new QualityModel(Quality.HDTV720p, new Revision(version: 1)), + new List()) .Should().BeFalse(); } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs index 7d9c2f0fc..074f587d1 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs @@ -2,13 +2,16 @@ using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; +using Moq; using NUnit.Framework; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Movies; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Queue; +using NzbDrone.Core.Test.CustomFormats; using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.DecisionEngineTests @@ -26,10 +29,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests { Mocker.Resolve(); + CustomFormatsFixture.GivenCustomFormats(CustomFormat.None); + _movie = Builder.CreateNew() .With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities(), + FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"), + FormatCutoff = CustomFormat.None.Id, UpgradeAllowed = true }) .Build(); @@ -39,9 +46,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .Build(); _remoteMovie = Builder.CreateNew() - .With(r => r.Movie = _movie) - .With(r => r.ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.DVD) }) - .Build(); + .With(r => r.Movie = _movie) + .With(r => r.ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.DVD) }) + .With(x => x.CustomFormats = new List { CustomFormat.None }) + .Build(); + + Mocker.GetMock() + .Setup(x => x.ParseCustomFormat(It.IsAny())) + .Returns(new List()); } private void GivenEmptyQueue() @@ -87,12 +99,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _movie.Profile.Cutoff = Quality.Bluray1080p.Id; var remoteMovie = Builder.CreateNew() - .With(r => r.Movie = _movie) - .With(r => r.ParsedMovieInfo = new ParsedMovieInfo - { - Quality = new QualityModel(Quality.SDTV) - }) - .Build(); + .With(r => r.Movie = _movie) + .With(r => r.ParsedMovieInfo = new ParsedMovieInfo + { + Quality = new QualityModel(Quality.SDTV) + }) + .With(x => x.CustomFormats = new List { CustomFormat.None }) + .Build(); GivenQueue(new List { remoteMovie }); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs index 74aa8b901..fa401ee29 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs @@ -4,6 +4,7 @@ using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications.RssSync; using NzbDrone.Core.Download.Pending; @@ -75,7 +76,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync private void GivenUpgradeForExistingFile() { Mocker.GetMock() - .Setup(s => s.IsUpgradable(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.IsUpgradable(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny>())) .Returns(true); } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs index 4882e69b0..e86cc229c 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs @@ -1,13 +1,17 @@ using System; +using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; +using Moq; using NUnit.Framework; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Movies; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.CustomFormats; using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.DecisionEngineTests @@ -27,18 +31,30 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Mocker.Resolve(); _upgradeDisk = Mocker.Resolve(); + CustomFormatsFixture.GivenCustomFormats(CustomFormat.None); + _firstFile = new MovieFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now }; var fakeSeries = Builder.CreateNew() - .With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }) - .With(e => e.MovieFile = _firstFile) - .Build(); + .With(c => c.Profile = new Profile + { + Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities(), + FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"), + FormatCutoff = CustomFormat.None.Id + }) + .With(e => e.MovieFile = _firstFile) + .Build(); _parseResultSingle = new RemoteMovie { Movie = fakeSeries, ParsedMovieInfo = new ParsedMovieInfo() { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) }, + CustomFormats = new List() }; + + Mocker.GetMock() + .Setup(x => x.ParseCustomFormat(It.IsAny())) + .Returns(new List()); } private void WithFirstFileUpgradable() @@ -63,6 +79,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_not_be_upgradable_if_qualities_are_the_same() { + Mocker.GetMock() + .Setup(x => x.ParseCustomFormat(It.IsAny())) + .Returns(new List()); + _firstFile.Quality = new QualityModel(Quality.WEBDL1080p); _parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p); _upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); diff --git a/src/NzbDrone.Core.Test/Framework/MigrationTest.cs b/src/NzbDrone.Core.Test/Framework/MigrationTest.cs index c751bbf55..f738b8240 100644 --- a/src/NzbDrone.Core.Test/Framework/MigrationTest.cs +++ b/src/NzbDrone.Core.Test/Framework/MigrationTest.cs @@ -1,4 +1,5 @@ using System; +using System.Data; using FluentMigrator; using Microsoft.Extensions.Logging; using NUnit.Framework; @@ -11,18 +12,32 @@ namespace NzbDrone.Core.Test.Framework public abstract class MigrationTest : DbTest where TMigration : NzbDroneMigrationBase { - protected long MigrationVersion + protected long MigrationVersion => ((MigrationAttribute)Attribute.GetCustomAttribute(typeof(TMigration), typeof(MigrationAttribute))).Version; + + [SetUp] + public override void SetupDb() { - get - { - var attrib = (MigrationAttribute)Attribute.GetCustomAttribute(typeof(TMigration), typeof(MigrationAttribute)); - return attrib.Version; - } + SetupContainer(); } protected virtual IDirectDataMapper WithMigrationTestDb(Action beforeMigration = null) { - var db = WithTestDb(new MigrationContext(MigrationType, MigrationVersion) + return WithMigrationAction(beforeMigration).GetDirectDataMapper(); + } + + protected virtual IDbConnection WithDapperMigrationTestDb(Action beforeMigration = null) + { + return WithMigrationAction(beforeMigration).OpenConnection(); + } + + protected override void SetupLogging() + { + Mocker.SetConstant(Mocker.Resolve()); + } + + private ITestDatabase WithMigrationAction(Action beforeMigration = null) + { + return WithTestDb(new MigrationContext(MigrationType, MigrationVersion) { BeforeMigration = m => { @@ -33,19 +48,6 @@ namespace NzbDrone.Core.Test.Framework } } }); - - return db.GetDirectDataMapper(); - } - - protected override void SetupLogging() - { - Mocker.SetConstant(Mocker.Resolve()); - } - - [SetUp] - public override void SetupDb() - { - SetupContainer(); } } } diff --git a/src/NzbDrone.Core.Test/Framework/TestDatabase.cs b/src/NzbDrone.Core.Test/Framework/TestDatabase.cs index 4215a66c6..3fbfdf028 100644 --- a/src/NzbDrone.Core.Test/Framework/TestDatabase.cs +++ b/src/NzbDrone.Core.Test/Framework/TestDatabase.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Data; using System.Linq; using Moq; using NzbDrone.Core.Datastore; @@ -21,6 +22,7 @@ namespace NzbDrone.Core.Test.Framework void Delete(T childModel) where T : ModelBase, new(); IDirectDataMapper GetDirectDataMapper(); + IDbConnection OpenConnection(); } public class TestDatabase : ITestDatabase @@ -74,5 +76,10 @@ namespace NzbDrone.Core.Test.Framework { return new DirectDataMapper(_dbConnection); } + + public IDbConnection OpenConnection() + { + return _dbConnection.OpenConnection(); + } } } diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedMoviesFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedMoviesFixture.cs index 19377d08f..0427b750a 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedMoviesFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedMoviesFixture.cs @@ -7,6 +7,7 @@ using Moq; using NUnit.Framework; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; +using NzbDrone.Core.History; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.MovieImport; @@ -58,6 +59,10 @@ namespace NzbDrone.Core.Test.MediaFiles .Setup(s => s.UpgradeMovieFile(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new MovieFileMoveResult()); + Mocker.GetMock() + .Setup(x => x.FindByDownloadId(It.IsAny())) + .Returns(new List()); + _downloadClientItem = Builder.CreateNew().Build(); } diff --git a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQualityFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQualityFixture.cs index 993ed6eb8..5b75147dd 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQualityFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQualityFixture.cs @@ -31,10 +31,10 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators .Returns(AugmentQualityResult.ResolutionOnly((int)Resolution.R1080p, Confidence.MediaInfo)); _fileExtensionAugmenter.Setup(s => s.AugmentQuality(It.IsAny())) - .Returns(new AugmentQualityResult(Source.TV, Confidence.Fallback, (int)Resolution.R720p, Confidence.Fallback, Modifier.NONE, Confidence.Fallback, new Revision(), new List())); + .Returns(new AugmentQualityResult(Source.TV, Confidence.Fallback, (int)Resolution.R720p, Confidence.Fallback, Modifier.NONE, Confidence.Fallback, new Revision())); _nameAugmenter.Setup(s => s.AugmentQuality(It.IsAny())) - .Returns(new AugmentQualityResult(Source.TV, Confidence.Default, (int)Resolution.R480p, Confidence.Default, Modifier.NONE, Confidence.Default, new Revision(), new List())); + .Returns(new AugmentQualityResult(Source.TV, Confidence.Default, (int)Resolution.R480p, Confidence.Default, Modifier.NONE, Confidence.Default, new Revision())); } private void GivenAugmenters(params Mock[] mocks) diff --git a/src/NzbDrone.Core.Test/MediaFiles/UpdateMovieFileQualityServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/UpdateMovieFileQualityServiceFixture.cs deleted file mode 100644 index f2744f326..000000000 --- a/src/NzbDrone.Core.Test/MediaFiles/UpdateMovieFileQualityServiceFixture.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System.Collections.Generic; -using FizzWare.NBuilder; -using Moq; -using NUnit.Framework; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.CustomFormats; -using NzbDrone.Core.History; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.MediaFiles.Commands; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Test.Common; - -namespace NzbDrone.Core.Test.MediaFiles -{ - [TestFixture] - public class UpdateMovieFileQualityServiceFixture : CoreTest - { - private MovieFile _movieFile; - private QualityModel _oldQuality; - private QualityModel _newQuality; - - private ParsedMovieInfo _newInfo; - - [SetUp] - public void Setup() - { - _movieFile = Builder.CreateNew().With(m => m.MovieId = 0).Build(); - - _oldQuality = new QualityModel(Quality.Bluray720p); - - _movieFile.Quality = _oldQuality; - - _newQuality = _oldQuality.JsonClone(); - var format = new CustomFormat("Awesome Format"); - format.Id = 1; - _newQuality.CustomFormats = new List { format }; - - _newInfo = new ParsedMovieInfo - { - Quality = _newQuality - }; - - Mocker.GetMock().Setup(s => s.GetMovies(It.IsAny>())) - .Returns(new List { _movieFile }); - - Mocker.GetMock().Setup(s => s.GetByMovieId(It.IsAny(), null)) - .Returns(new List()); - } - - private void ExecuteCommand() - { - Subject.Execute(new UpdateMovieFileQualityCommand(new List { 0 })); - } - - [Test] - public void should_not_update_if_unable_to_parse() - { - ExecuteCommand(); - - ExceptionVerification.ExpectedWarns(1); - - Mocker.GetMock().Verify(s => s.Update(It.IsAny()), Times.Never()); - } - - [Test] - public void should_update_with_new_formats() - { - Mocker.GetMock().Setup(s => s.ParseMovieInfo(It.IsAny(), It.IsAny>())) - .Returns(_newInfo); - - ExecuteCommand(); - - Mocker.GetMock().Verify(s => s.Update(It.Is(f => f.Quality.CustomFormats == _newQuality.CustomFormats)), Times.Once()); - } - - [Test] - public void should_use_imported_history_title() - { - var imported = Builder.CreateNew() - .With(h => h.EventType = HistoryEventType.DownloadFolderImported) - .With(h => h.SourceTitle = "My Movie 2018.mkv").Build(); - - Mocker.GetMock().Setup(s => s.GetByMovieId(It.IsAny(), null)) - .Returns(new List { imported }); - - Mocker.GetMock().Setup(s => s.ParseMovieInfo("My Movie 2018.mkv", It.IsAny>())) - .Returns(_newInfo); - - ExecuteCommand(); - - Mocker.GetMock().Verify(s => s.ParseMovieInfo("My Movie 2018.mkv", It.IsAny>())); - } - } -} diff --git a/src/NzbDrone.Core.Test/MovieTests/MovieRepositoryTests/MovieRepositoryFixture.cs b/src/NzbDrone.Core.Test/MovieTests/MovieRepositoryTests/MovieRepositoryFixture.cs index 8d5cc30cb..dac3df416 100644 --- a/src/NzbDrone.Core.Test/MovieTests/MovieRepositoryTests/MovieRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/MovieTests/MovieRepositoryTests/MovieRepositoryFixture.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; @@ -22,19 +23,23 @@ namespace NzbDrone.Core.Test.MovieTests.MovieRepositoryTests { _profileRepository = Mocker.Resolve(); Mocker.SetConstant(_profileRepository); + + Mocker.GetMock() + .Setup(x => x.All()) + .Returns(new List()); } [Test] public void should_load_quality_profile() { var profile = new Profile - { - Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p), - FormatItems = CustomFormatsFixture.GetDefaultFormatItems(), - FormatCutoff = CustomFormat.None.Id, - Cutoff = Quality.Bluray1080p.Id, - Name = "TestProfile" - }; + { + Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p), + FormatItems = CustomFormatsFixture.GetDefaultFormatItems(), + FormatCutoff = CustomFormat.None.Id, + Cutoff = Quality.Bluray1080p.Id, + Name = "TestProfile" + }; _profileRepository.Insert(profile); diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/AugmentersTests/AugmentWithParsedMovieInfo.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/AugmentersTests/AugmentWithParsedMovieInfo.cs index 203c99b6e..d428345da 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/AugmentersTests/AugmentWithParsedMovieInfo.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/AugmentersTests/AugmentWithParsedMovieInfo.cs @@ -1,11 +1,9 @@ using System.Collections.Generic; using FluentAssertions; using NUnit.Framework; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Languages; using NzbDrone.Core.Parser.Augmenters; using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests { @@ -61,37 +59,6 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests result.Languages.Should().BeEquivalentTo(Language.English, Language.French); } - [Test] - public void should_combine_formats() - { - var folderInfo = new ParsedMovieInfo - { - Quality = new QualityModel(Quality.Bluray1080p) - }; - - var format1 = new CustomFormat("Awesome Format"); - format1.Id = 1; - - var format2 = new CustomFormat("Cool Format"); - format2.Id = 2; - - folderInfo.Quality.CustomFormats = new List { format1 }; - - MovieInfo.Quality.CustomFormats = new List { format2 }; - - var result = Subject.AugmentMovieInfo(MovieInfo, folderInfo); - - result.Quality.CustomFormats.Count.Should().Be(2); - result.Quality.CustomFormats.Should().BeEquivalentTo(format2, format1); - - folderInfo.Quality.CustomFormats = new List { format1, format2 }; - - result = Subject.AugmentMovieInfo(MovieInfo, folderInfo); - - result.Quality.CustomFormats.Count.Should().Be(2); - result.Quality.CustomFormats.Should().BeEquivalentTo(format2, format1); - } - [Test] public void should_use_folder_release_group() { diff --git a/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs index 1fe68807c..b4a7cf9f2 100644 --- a/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs +++ b/src/NzbDrone.Core.Test/Qualities/QualityModelComparerFixture.cs @@ -1,10 +1,8 @@ using System.Collections.Generic; using FluentAssertions; using NUnit.Framework; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; -using NzbDrone.Core.Test.CustomFormats; using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.Qualities @@ -14,11 +12,6 @@ namespace NzbDrone.Core.Test.Qualities { public QualityModelComparer Subject { get; set; } - private CustomFormat _customFormat1; - private CustomFormat _customFormat2; - private CustomFormat _customFormat3; - private CustomFormat _customFormat4; - [SetUp] public void Setup() { @@ -78,18 +71,6 @@ namespace NzbDrone.Core.Test.Qualities Subject = new QualityModelComparer(profile); } - private void GivenDefaultProfileWithFormats() - { - _customFormat1 = new CustomFormat("My Format 1", "L_ENGLISH") { Id = 1 }; - _customFormat2 = new CustomFormat("My Format 2", "L_FRENCH") { Id = 2 }; - _customFormat3 = new CustomFormat("My Format 3", "L_SPANISH") { Id = 3 }; - _customFormat4 = new CustomFormat("My Format 4", "L_ITALIAN") { Id = 4 }; - - CustomFormatsFixture.GivenCustomFormats(CustomFormat.None, _customFormat1, _customFormat2, _customFormat3, _customFormat4); - - Subject = new QualityModelComparer(new Profile { Items = QualityFixture.GetDefaultQualities(), FormatItems = CustomFormatsFixture.GetSampleFormatItems(), FormatCutoff = _customFormat2.Id }); - } - [Test] public void should_be_greater_when_first_quality_is_greater_than_second() { @@ -142,32 +123,6 @@ namespace NzbDrone.Core.Test.Qualities compare.Should().BeGreaterThan(0); } - [Test] - public void should_be_lesser_when_first_quality_is_worse_format() - { - GivenDefaultProfileWithFormats(); - - var first = new QualityModel(Quality.DVD) { CustomFormats = new List { _customFormat1 } }; - var second = new QualityModel(Quality.DVD) { CustomFormats = new List { _customFormat2 } }; - - var compare = Subject.Compare(first, second); - - compare.Should().BeLessThan(0); - } - - [Test] - public void should_be_greater_when_first_quality_is_better_format() - { - GivenDefaultProfileWithFormats(); - - var first = new QualityModel(Quality.DVD) { CustomFormats = new List { _customFormat2 } }; - var second = new QualityModel(Quality.DVD) { CustomFormats = new List { _customFormat1 } }; - - var compare = Subject.Compare(first, second); - - compare.Should().BeGreaterThan(0); - } - [Test] public void should_ignore_group_order_by_default() { @@ -193,57 +148,5 @@ namespace NzbDrone.Core.Test.Qualities compare.Should().BeLessThan(0); } - - [Test] - public void should_be_greater_when_one_format_over_cutoff() - { - GivenDefaultProfileWithFormats(); - - var first = new List { _customFormat3 }; - var second = _customFormat2.Id; - - var compare = Subject.Compare(first, second); - - compare.Should().BeGreaterThan(0); - } - - [Test] - public void should_be_greater_when_multiple_formats_over_cutoff() - { - GivenDefaultProfileWithFormats(); - - var first = new List { _customFormat3, _customFormat4 }; - var second = _customFormat2.Id; - - var compare = Subject.Compare(first, second); - - compare.Should().BeGreaterThan(0); - } - - [Test] - public void should_be_greater_when_one_better_one_worse_than_cutoff() - { - GivenDefaultProfileWithFormats(); - - var first = new List { _customFormat1, _customFormat3 }; - var second = _customFormat2.Id; - - var compare = Subject.Compare(first, second); - - compare.Should().BeGreaterThan(0); - } - - [Test] - public void should_be_zero_when_one_worse_one_equal_to_cutoff() - { - GivenDefaultProfileWithFormats(); - - var first = new List { _customFormat1, _customFormat2 }; - var second = _customFormat2.Id; - - var compare = Subject.Compare(first, second); - - compare.Should().Be(0); - } } } diff --git a/src/NzbDrone.Core/Blacklisting/Blacklist.cs b/src/NzbDrone.Core/Blacklisting/Blacklist.cs index c041126cf..2a3496cce 100644 --- a/src/NzbDrone.Core/Blacklisting/Blacklist.cs +++ b/src/NzbDrone.Core/Blacklisting/Blacklist.cs @@ -4,6 +4,7 @@ using NzbDrone.Core.Datastore; using NzbDrone.Core.Indexers; using NzbDrone.Core.Languages; using NzbDrone.Core.Movies; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Blacklisting @@ -19,6 +20,7 @@ namespace NzbDrone.Core.Blacklisting public long? Size { get; set; } public DownloadProtocol Protocol { get; set; } public string Indexer { get; set; } + public IndexerFlags IndexerFlags { get; set; } public string Message { get; set; } public string TorrentInfoHash { get; set; } public List Languages { get; set; } diff --git a/src/NzbDrone.Core/Blacklisting/BlacklistService.cs b/src/NzbDrone.Core/Blacklisting/BlacklistService.cs index fb9bd3ad6..97e917d65 100644 --- a/src/NzbDrone.Core/Blacklisting/BlacklistService.cs +++ b/src/NzbDrone.Core/Blacklisting/BlacklistService.cs @@ -152,6 +152,11 @@ namespace NzbDrone.Core.Blacklisting Languages = message.Languages }; + if (Enum.TryParse(message.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags)) + { + blacklist.IndexerFlags = flags; + } + _blacklistRepository.Insert(blacklist); } diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormat.cs b/src/NzbDrone.Core/CustomFormats/CustomFormat.cs index 5d9c0a62e..68fb488d7 100644 --- a/src/NzbDrone.Core/CustomFormats/CustomFormat.cs +++ b/src/NzbDrone.Core/CustomFormats/CustomFormat.cs @@ -7,10 +7,6 @@ namespace NzbDrone.Core.CustomFormats { public class CustomFormat : ModelBase, IEquatable { - public string Name { get; set; } - - public List FormatTags { get; set; } - public CustomFormat() { } @@ -21,18 +17,25 @@ namespace NzbDrone.Core.CustomFormats FormatTags = tags.Select(t => new FormatTag(t)).ToList(); } - public static implicit operator CustomFormatDefinition(CustomFormat format) => new CustomFormatDefinition { Id = format.Id, Name = format.Name, FormatTags = format.FormatTags }; + public static CustomFormat None => new CustomFormat + { + Id = 0, + Name = "None", + FormatTags = new List() + }; + + public string Name { get; set; } + + public List FormatTags { get; set; } public override string ToString() { return Name; } - public static CustomFormat None => new CustomFormat("None"); - public bool Equals(CustomFormat other) { - if (ReferenceEquals(null, other)) + if (other is null) { return false; } @@ -47,7 +50,7 @@ namespace NzbDrone.Core.CustomFormats public override bool Equals(object obj) { - if (ReferenceEquals(null, obj)) + if (obj is null) { return false; } @@ -67,26 +70,7 @@ namespace NzbDrone.Core.CustomFormats public override int GetHashCode() { - return Id.GetHashCode(); - } - } - - public static class CustomFormatExtensions - { - public static string ToExtendedString(this IEnumerable formats) - { - return string.Join(", ", formats.Select(f => f.ToString())); - } - - public static List WithNone(this IEnumerable formats) - { - var list = formats.ToList(); - if (list.Any()) - { - return list; - } - - return new List { CustomFormat.None }; + return Id; } } } diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs new file mode 100644 index 000000000..9bd848d0f --- /dev/null +++ b/src/NzbDrone.Core/CustomFormats/CustomFormatCalculationService.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Blacklisting; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.CustomFormats +{ + public interface ICustomFormatCalculationService + { + List ParseCustomFormat(ParsedMovieInfo movieInfo); + List ParseCustomFormat(MovieFile movieFile); + List ParseCustomFormat(Blacklist blacklist); + List ParseCustomFormat(History.History history); + List MatchFormatTags(ParsedMovieInfo movieInfo); + } + + public class CustomFormatCalculationService : ICustomFormatCalculationService + { + private readonly ICustomFormatService _formatService; + private readonly IParsingService _parsingService; + private readonly IMovieService _movieService; + + public CustomFormatCalculationService(ICustomFormatService formatService, + IParsingService parsingService, + IMovieService movieService) + { + _formatService = formatService; + _parsingService = parsingService; + _movieService = movieService; + } + + public List ParseCustomFormat(ParsedMovieInfo movieInfo) + { + return MatchFormatTags(movieInfo) + .Where(m => m.GoodMatch) + .Select(r => r.CustomFormat) + .ToList(); + } + + public List ParseCustomFormat(MovieFile movieFile) + { + return MatchFormatTags(movieFile) + .Where(m => m.GoodMatch) + .Select(r => r.CustomFormat) + .ToList(); + } + + public List ParseCustomFormat(Blacklist blacklist) + { + return MatchFormatTags(blacklist) + .Where(m => m.GoodMatch) + .Select(r => r.CustomFormat) + .ToList(); + } + + public List ParseCustomFormat(History.History history) + { + return MatchFormatTags(history) + .Where(m => m.GoodMatch) + .Select(r => r.CustomFormat) + .ToList(); + } + + public List MatchFormatTags(ParsedMovieInfo movieInfo) + { + var formats = _formatService.All(); + + var matches = new List(); + + foreach (var customFormat in formats) + { + var tagTypeMatches = customFormat.FormatTags + .GroupBy(t => t.TagType) + .Select(g => new FormatTagMatchesGroup + { + Type = g.Key, + Matches = g.ToDictionary(t => t, t => t.DoesItMatch(movieInfo)) + }) + .ToList(); + + matches.Add(new CustomFormatMatchResult + { + CustomFormat = customFormat, + GroupMatches = tagTypeMatches + }); + } + + return matches; + } + + private List MatchFormatTags(MovieFile file) + { + var info = new ParsedMovieInfo + { + MovieTitle = file.Movie.Title, + SimpleReleaseTitle = file.GetSceneOrFileName().SimplifyReleaseTitle(), + Quality = file.Quality, + Languages = file.Languages, + ReleaseGroup = file.ReleaseGroup, + Edition = file.Edition, + Year = file.Movie.Year, + ImdbId = file.Movie.ImdbId, + ExtraInfo = new Dictionary + { + { "IndexerFlags", file.IndexerFlags }, + { "Size", file.Size }, + { "Filename", System.IO.Path.GetFileName(file.RelativePath) } + } + }; + + return MatchFormatTags(info); + } + + private List MatchFormatTags(Blacklist blacklist) + { + var parsed = _parsingService.ParseMovieInfo(blacklist.SourceTitle, null); + + var info = new ParsedMovieInfo + { + MovieTitle = blacklist.Movie.Title, + SimpleReleaseTitle = parsed?.SimpleReleaseTitle ?? blacklist.SourceTitle.SimplifyReleaseTitle(), + Quality = blacklist.Quality, + Languages = blacklist.Languages, + ReleaseGroup = parsed?.ReleaseGroup, + Edition = parsed?.Edition, + Year = blacklist.Movie.Year, + ImdbId = blacklist.Movie.ImdbId, + ExtraInfo = new Dictionary + { + { "IndexerFlags", blacklist.IndexerFlags }, + { "Size", blacklist.Size } + } + }; + + return MatchFormatTags(info); + } + + private List MatchFormatTags(History.History history) + { + var movie = _movieService.GetMovie(history.MovieId); + var parsed = _parsingService.ParseMovieInfo(history.SourceTitle, null); + + Enum.TryParse(history.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags); + int.TryParse(history.Data.GetValueOrDefault("size"), out var size); + + var info = new ParsedMovieInfo + { + MovieTitle = movie.Title, + SimpleReleaseTitle = parsed?.SimpleReleaseTitle ?? history.SourceTitle.SimplifyReleaseTitle(), + Quality = history.Quality, + Languages = history.Languages, + ReleaseGroup = parsed?.ReleaseGroup, + Edition = parsed?.Edition, + Year = movie.Year, + ImdbId = movie.ImdbId, + ExtraInfo = new Dictionary + { + { "IndexerFlags", flags }, + { "Size", size } + } + }; + + return MatchFormatTags(info); + } + } +} diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatDefinition.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatDefinition.cs deleted file mode 100644 index 029699c68..000000000 --- a/src/NzbDrone.Core/CustomFormats/CustomFormatDefinition.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Core.Datastore; - -namespace NzbDrone.Core.CustomFormats -{ - public class CustomFormatDefinition : ModelBase - { - public string Name { get; set; } - - public List FormatTags { get; set; } - - public static implicit operator CustomFormat(CustomFormatDefinition def) => new CustomFormat { Id = def.Id, Name = def.Name, FormatTags = def.FormatTags }; - } -} diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatMatchResult.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatMatchResult.cs new file mode 100644 index 000000000..74df54381 --- /dev/null +++ b/src/NzbDrone.Core/CustomFormats/CustomFormatMatchResult.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Core.CustomFormats +{ + public class CustomFormatMatchResult + { + public CustomFormat CustomFormat { get; set; } + + public List GroupMatches { get; set; } + + public bool GoodMatch => GroupMatches.All(g => g.DidMatch); + } +} diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatRepository.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatRepository.cs index 01ec3d00e..0a8e868d9 100644 --- a/src/NzbDrone.Core/CustomFormats/CustomFormatRepository.cs +++ b/src/NzbDrone.Core/CustomFormats/CustomFormatRepository.cs @@ -3,11 +3,11 @@ using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.CustomFormats { - public interface ICustomFormatRepository : IBasicRepository + public interface ICustomFormatRepository : IBasicRepository { } - public class CustomFormatRepository : BasicRepository, ICustomFormatRepository + public class CustomFormatRepository : BasicRepository, ICustomFormatRepository { public CustomFormatRepository(IMainDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatService.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatService.cs index ce85b69eb..7e0df81d6 100644 --- a/src/NzbDrone.Core/CustomFormats/CustomFormatService.cs +++ b/src/NzbDrone.Core/CustomFormats/CustomFormatService.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using NLog; using NzbDrone.Common.Cache; -using NzbDrone.Common.Composition; -using NzbDrone.Core.Blacklisting; +using NzbDrone.Core.CustomFormats.Events; using NzbDrone.Core.Datastore; -using NzbDrone.Core.History; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.Profiles; +using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.CustomFormats { @@ -24,162 +19,62 @@ namespace NzbDrone.Core.CustomFormats public class CustomFormatService : ICustomFormatService { private readonly ICustomFormatRepository _formatRepository; - private readonly IHistoryService _historyService; - private IProfileService _profileService; - - public IProfileService ProfileService - { - get - { - if (_profileService == null) - { - _profileService = _container.Resolve(); - } - - return _profileService; - } - } - - private readonly IContainer _container; + private readonly IEventAggregator _eventAggregator; private readonly ICached> _cache; - private readonly Logger _logger; - - public static Dictionary AllCustomFormats; public CustomFormatService(ICustomFormatRepository formatRepository, - ICacheManager cacheManager, - IContainer container, - IHistoryService historyService, - Logger logger) + ICacheManager cacheManager, + IEventAggregator eventAggregator) { _formatRepository = formatRepository; - _container = container; + _eventAggregator = eventAggregator; _cache = cacheManager.GetCache>(typeof(CustomFormat), "formats"); - _historyService = historyService; - _logger = logger; - - // Fill up the cache for subsequent DB lookups - All(); } - public void Update(CustomFormat customFormat) + private Dictionary AllDictionary() { - _formatRepository.Update(customFormat); - _cache.Clear(); + return _cache.Get("all", () => _formatRepository.All().ToDictionary(m => m.Id)); } - public CustomFormat Insert(CustomFormat customFormat) + public List All() { - var ret = _formatRepository.Insert(customFormat); - try - { - ProfileService.AddCustomFormat(ret); - } - catch (Exception e) - { - _logger.Error(e, "Failure while trying to add the new custom format to all profiles. Deleting again!"); - _formatRepository.Delete(ret); - throw; - } - - _cache.Clear(); - return ret; + return AllDictionary().Values.ToList(); } - public void Delete(int id) + public CustomFormat GetById(int id) { - _cache.Clear(); - try - { - //First history: - var historyRepo = _container.Resolve(); - DeleteInRepo(historyRepo, - h => h.Quality.CustomFormats, - (h, f) => - { - h.Quality.CustomFormats = f; - return h; - }, - id); - - //Then Blacklist: - var blacklistRepo = _container.Resolve(); - DeleteInRepo(blacklistRepo, - h => h.Quality.CustomFormats, - (h, f) => - { - h.Quality.CustomFormats = f; - return h; - }, - id); - - //Then MovieFiles: - var moviefileRepo = _container.Resolve(); - DeleteInRepo(moviefileRepo, - h => h.Quality.CustomFormats, - (h, f) => - { - h.Quality.CustomFormats = f; - return h; - }, - id); - - //Then Profiles - ProfileService.DeleteCustomFormat(id); - } - catch (Exception e) - { - _logger.Error(e, "Failed to delete format with id {} from other repositories! Format will not be deleted!", id); - throw; - } - - //Finally delete the format for real! - _formatRepository.Delete(id); + return AllDictionary()[id]; + } + public void Update(CustomFormat customFormat) + { + _formatRepository.Update(customFormat); _cache.Clear(); } - private void DeleteInRepo(IBasicRepository repository, - Func> queryFunc, - Func, TModel> updateFunc, - int customFormatId) - where TModel : ModelBase, new() + public CustomFormat Insert(CustomFormat customFormat) { - var allItems = repository.All(); + // Add to DB then insert into profiles + var result = _formatRepository.Insert(customFormat); + _cache.Clear(); - var toUpdate = allItems.Where(r => queryFunc(r).Exists(c => c.Id == customFormatId)).Select(r => - { - return updateFunc(r, queryFunc(r).Where(c => c.Id != customFormatId).ToList()); - }); + _eventAggregator.PublishEvent(new CustomFormatAddedEvent(result)); - repository.UpdateMany(toUpdate.ToList()); + return result; } - private Dictionary AllDictionary() + public void Delete(int id) { - return _cache.Get("all", () => - { - var all = _formatRepository.All().Select(x => (CustomFormat)x).ToDictionary(m => m.Id); - AllCustomFormats = all; - return all; - }); - } + var format = _formatRepository.Get(id); - public List All() - { - return AllDictionary().Values.ToList(); - } + // Remove from profiles before removing from DB + _eventAggregator.PublishEvent(new CustomFormatDeletedEvent(format)); - public CustomFormat GetById(int id) - { - return AllDictionary()[id]; + _formatRepository.Delete(id); + _cache.Clear(); } - public static Dictionary> Templates - { - get - { - return new Dictionary> + public static Dictionary> Templates => new Dictionary> { { "Easy", new List @@ -207,7 +102,5 @@ namespace NzbDrone.Core.CustomFormats } } }; - } - } } } diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatsComparer.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatsComparer.cs new file mode 100644 index 000000000..b8cd09b9b --- /dev/null +++ b/src/NzbDrone.Core/CustomFormats/CustomFormatsComparer.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.EnsureThat; +using NzbDrone.Core.Profiles; + +namespace NzbDrone.Core.CustomFormats +{ + public class CustomFormatsComparer : IComparer> + { + private readonly Profile _profile; + + public CustomFormatsComparer(Profile profile) + { + Ensure.That(profile, () => profile).IsNotNull(); + Ensure.That(profile.Items, () => profile.Items).HasItems(); + + _profile = profile; + } + + public int Compare(List left, List right) + { + var leftIndicies = _profile.GetIndices(left); + var rightIndicies = _profile.GetIndices(right); + + // Summing powers of two ensures last format always trumps, but we order correctly if we + // have extra formats lower down the list + var leftTotal = leftIndicies.Select(x => Math.Pow(2, x)).Sum(); + var rightTotal = rightIndicies.Select(x => Math.Pow(2, x)).Sum(); + + return leftTotal.CompareTo(rightTotal); + } + } +} diff --git a/src/NzbDrone.Core/CustomFormats/Events/CustomFormatAddedEvent.cs b/src/NzbDrone.Core/CustomFormats/Events/CustomFormatAddedEvent.cs new file mode 100644 index 000000000..975d57105 --- /dev/null +++ b/src/NzbDrone.Core/CustomFormats/Events/CustomFormatAddedEvent.cs @@ -0,0 +1,14 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.CustomFormats.Events +{ + public class CustomFormatAddedEvent : IEvent + { + public CustomFormatAddedEvent(CustomFormat format) + { + CustomFormat = format; + } + + public CustomFormat CustomFormat { get; private set; } + } +} diff --git a/src/NzbDrone.Core/CustomFormats/Events/CustomFormatDeletedEvent.cs b/src/NzbDrone.Core/CustomFormats/Events/CustomFormatDeletedEvent.cs new file mode 100644 index 000000000..50991b1b1 --- /dev/null +++ b/src/NzbDrone.Core/CustomFormats/Events/CustomFormatDeletedEvent.cs @@ -0,0 +1,14 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.CustomFormats.Events +{ + public class CustomFormatDeletedEvent : IEvent + { + public CustomFormatDeletedEvent(CustomFormat format) + { + CustomFormat = format; + } + + public CustomFormat CustomFormat { get; private set; } + } +} diff --git a/src/NzbDrone.Core/CustomFormats/FormatTag.cs b/src/NzbDrone.Core/CustomFormats/FormatTag.cs index a9de543bb..82c3c0900 100644 --- a/src/NzbDrone.Core/CustomFormats/FormatTag.cs +++ b/src/NzbDrone.Core/CustomFormats/FormatTag.cs @@ -7,20 +7,21 @@ using NzbDrone.Common.Extensions; using NzbDrone.Core.Languages; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Qualities; namespace NzbDrone.Core.CustomFormats { public class FormatTag { - public string Raw { get; set; } - public TagType TagType { get; set; } - public TagModifier TagModifier { get; set; } - public object Value { get; set; } - public static Regex QualityTagRegex = new Regex(@"^(?R|S|M|E|L|C|I|G)(_((?RX)|(?RQ)|(?N)){0,3})?_(?.*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); public static Regex SizeTagRegex = new Regex(@"(?\d+(\.\d+)?)\s*<>\s*(?\d+(\.\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + // This function is needed for json deserialization to work. + public FormatTag() + { + } + public FormatTag(string raw) { Raw = raw; @@ -31,13 +32,13 @@ namespace NzbDrone.Core.CustomFormats throw new ArgumentException("Quality Tag is not in the correct format!"); } - ParseRawMatch(match); + ParseFormatTagString(match); } - // This function is needed for json deserialization to work. - private FormatTag() - { - } + public string Raw { get; set; } + public TagType TagType { get; set; } + public TagModifier TagModifier { get; set; } + public object Value { get; set; } public bool DoesItMatch(ParsedMovieInfo movieInfo) { @@ -50,59 +51,64 @@ namespace NzbDrone.Core.CustomFormats return match; } + private bool MatchString(string compared) + { + if (compared == null) + { + return false; + } + + if (TagModifier.HasFlag(TagModifier.Regex)) + { + var regexValue = (Regex)Value; + return regexValue.IsMatch(compared); + } + else + { + var stringValue = (string)Value; + return compared.ToLower().Contains(stringValue.Replace(" ", string.Empty).ToLower()); + } + } + private bool DoesItMatchWithoutMods(ParsedMovieInfo movieInfo) { + if (movieInfo == null) + { + return false; + } + + var filename = (string)movieInfo?.ExtraInfo?.GetValueOrDefault("Filename"); + switch (TagType) { case TagType.Edition: + return MatchString(movieInfo.Edition); case TagType.Custom: - string compared = null; - if (TagType == TagType.Custom) - { - compared = movieInfo.SimpleReleaseTitle; - } - else - { - compared = movieInfo.Edition; - } - - if (TagModifier.HasFlag(TagModifier.Regex)) - { - Regex regexValue = (Regex)Value; - return regexValue.IsMatch(compared); - } - else - { - string stringValue = (string)Value; - return compared.ToLower().Contains(stringValue.Replace(" ", string.Empty).ToLower()); - } - + return MatchString(movieInfo.SimpleReleaseTitle) || MatchString(filename); case TagType.Language: - return movieInfo.Languages.Contains((Language)Value); + return movieInfo?.Languages?.Contains((Language)Value) ?? false; case TagType.Resolution: - return movieInfo.Quality.Quality.Resolution == (int)(Resolution)Value; + return (movieInfo?.Quality?.Quality?.Resolution ?? (int)Resolution.Unknown) == (int)(Resolution)Value; case TagType.Modifier: - return movieInfo.Quality.Quality.Modifier == (Modifier)Value; + return (movieInfo?.Quality?.Quality?.Modifier ?? (int)Modifier.NONE) == (Modifier)Value; case TagType.Source: - return movieInfo.Quality.Quality.Source == (Source)Value; + return (movieInfo?.Quality?.Quality?.Source ?? (int)Source.UNKNOWN) == (Source)Value; case TagType.Size: - var size = (movieInfo.ExtraInfo.GetValueOrDefault("Size", 0.0) as long?) ?? 0; + var size = (movieInfo?.ExtraInfo?.GetValueOrDefault("Size", 0.0) as long?) ?? 0; var tuple = Value as (long, long)? ?? (0, 0); return size > tuple.Item1 && size < tuple.Item2; case TagType.Indexer: #if !LIBRARY - return (movieInfo.ExtraInfo.GetValueOrDefault("IndexerFlags") as IndexerFlags?)?.HasFlag((IndexerFlags)Value) == true; + var flags = movieInfo?.ExtraInfo?.GetValueOrDefault("IndexerFlags") as IndexerFlags?; + return flags?.HasFlag((IndexerFlags)Value) == true; #endif default: return false; } } - private void ParseRawMatch(Match match) + private void ParseTagModifier(Match match) { - var type = match.Groups["type"].Value.ToLower(); - var value = match.Groups["value"].Value.ToLower(); - if (match.Groups["m_re"].Success) { TagModifier |= TagModifier.AbsolutelyRequired; @@ -117,137 +123,169 @@ namespace NzbDrone.Core.CustomFormats { TagModifier |= TagModifier.Not; } + } + + private void ParseResolutionType(string value) + { + TagType = TagType.Resolution; + switch (value) + { + case "2160": + Value = Resolution.R2160p; + break; + case "1080": + Value = Resolution.R1080p; + break; + case "720": + Value = Resolution.R720p; + break; + case "576": + Value = Resolution.R576p; + break; + case "480": + Value = Resolution.R480p; + break; + default: + break; + } + } + + private void ParseSourceType(string value) + { + TagType = TagType.Source; + switch (value) + { + case "cam": + Value = Source.CAM; + break; + case "telesync": + Value = Source.TELESYNC; + break; + case "telecine": + Value = Source.TELECINE; + break; + case "workprint": + Value = Source.WORKPRINT; + break; + case "dvd": + Value = Source.DVD; + break; + case "tv": + Value = Source.TV; + break; + case "webdl": + Value = Source.WEBDL; + break; + case "bluray": + Value = Source.BLURAY; + break; + default: + break; + } + } + + private void ParseModifierType(string value) + { + TagType = TagType.Modifier; + switch (value) + { + case "regional": + Value = Modifier.REGIONAL; + break; + case "screener": + Value = Modifier.SCREENER; + break; + case "rawhd": + Value = Modifier.RAWHD; + break; + case "brdisk": + Value = Modifier.BRDISK; + break; + case "remux": + Value = Modifier.REMUX; + break; + default: + break; + } + } + + private void ParseIndexerFlagType(string value) + { + TagType = TagType.Indexer; + var flagValues = Enum.GetValues(typeof(IndexerFlags)); + + foreach (IndexerFlags flagValue in flagValues) + { + var flagString = flagValue.ToString(); + if (flagString.ToLower().Replace("_", string.Empty) != value.ToLower().Replace("_", string.Empty)) + { + continue; + } + + Value = flagValue; + break; + } + } + + private void ParseSizeType(string value) + { + TagType = TagType.Size; + var matches = SizeTagRegex.Match(value); + var min = double.Parse(matches.Groups["min"].Value, CultureInfo.InvariantCulture); + var max = double.Parse(matches.Groups["max"].Value, CultureInfo.InvariantCulture); + Value = (min.Gigabytes(), max.Gigabytes()); + } + + private void ParseString(string value) + { + if (TagModifier.HasFlag(TagModifier.Regex)) + { + Value = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase); + } + else + { + Value = value; + } + } + + private void ParseFormatTagString(Match match) + { + ParseTagModifier(match); + + var type = match.Groups["type"].Value.ToLower(); + var value = match.Groups["value"].Value.ToLower(); switch (type) { case "r": - TagType = TagType.Resolution; - switch (value) - { - case "2160": - Value = Resolution.R2160p; - break; - case "1080": - Value = Resolution.R1080p; - break; - case "720": - Value = Resolution.R720p; - break; - case "576": - Value = Resolution.R576p; - break; - case "480": - Value = Resolution.R480p; - break; - } - + ParseResolutionType(value); break; case "s": - TagType = TagType.Source; - switch (value) - { - case "cam": - Value = Source.CAM; - break; - case "telesync": - Value = Source.TELESYNC; - break; - case "telecine": - Value = Source.TELECINE; - break; - case "workprint": - Value = Source.WORKPRINT; - break; - case "dvd": - Value = Source.DVD; - break; - case "tv": - Value = Source.TV; - break; - case "webdl": - Value = Source.WEBDL; - break; - case "bluray": - Value = Source.BLURAY; - break; - } - + ParseSourceType(value); break; case "m": - TagType = TagType.Modifier; - switch (value) - { - case "regional": - Value = Modifier.REGIONAL; - break; - case "screener": - Value = Modifier.SCREENER; - break; - case "rawhd": - Value = Modifier.RAWHD; - break; - case "brdisk": - Value = Modifier.BRDISK; - break; - case "remux": - Value = Modifier.REMUX; - break; - } - + ParseModifierType(value); break; case "e": TagType = TagType.Edition; - if (TagModifier.HasFlag(TagModifier.Regex)) - { - Value = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase); - } - else - { - Value = value; - } - + ParseString(value); break; case "l": TagType = TagType.Language; - Value = Parser.LanguageParser.ParseLanguages(value).First(); + Value = LanguageParser.ParseLanguages(value).First(); break; case "i": #if !LIBRARY - TagType = TagType.Indexer; - var flagValues = Enum.GetValues(typeof(IndexerFlags)); - - foreach (IndexerFlags flagValue in flagValues) - { - var flagString = flagValue.ToString(); - if (flagString.ToLower().Replace("_", string.Empty) != value.ToLower().Replace("_", string.Empty)) - { - continue; - } - - Value = flagValue; - break; - } + ParseIndexerFlagType(value); #endif break; case "g": - TagType = TagType.Size; - var matches = SizeTagRegex.Match(value); - var min = double.Parse(matches.Groups["min"].Value, CultureInfo.InvariantCulture); - var max = double.Parse(matches.Groups["max"].Value, CultureInfo.InvariantCulture); - Value = (min.Gigabytes(), max.Gigabytes()); + ParseSizeType(value); break; case "c": default: TagType = TagType.Custom; - if (TagModifier.HasFlag(TagModifier.Regex)) - { - Value = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase); - } - else - { - Value = value; - } - + ParseString(value); break; } } @@ -272,28 +310,4 @@ namespace NzbDrone.Core.CustomFormats Not = 2, // Do not match AbsolutelyRequired = 4 } - - public enum Source - { - UNKNOWN = 0, - CAM, - TELESYNC, - TELECINE, - WORKPRINT, - DVD, - TV, - WEBDL, - WEBRIP, - BLURAY - } - - public enum Modifier - { - NONE = 0, - REGIONAL, - SCREENER, - RAWHD, - BRDISK, - REMUX - } } diff --git a/src/NzbDrone.Core/CustomFormats/FormatTagMatchResult.cs b/src/NzbDrone.Core/CustomFormats/FormatTagMatchResult.cs deleted file mode 100644 index ecb14dbd7..000000000 --- a/src/NzbDrone.Core/CustomFormats/FormatTagMatchResult.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace NzbDrone.Core.CustomFormats -{ - public class FormatTagMatchResult - { - public FormatTagMatchResult() - { - GroupMatches = new List(); - } - - public CustomFormat CustomFormat { get; set; } - public List GroupMatches { get; set; } - public bool GoodMatch { get; set; } - } - - public class FormatTagMatchesGroup - { - public FormatTagMatchesGroup() - { - Matches = new Dictionary(); - } - - public FormatTagMatchesGroup(TagType type, Dictionary matches) - { - Type = type; - Matches = matches; - } - - public TagType Type { get; set; } - - public bool DidMatch - { - get - { - return !(Matches.Any(m => m.Key.TagModifier.HasFlag(TagModifier.AbsolutelyRequired) && m.Value == false) || - Matches.All(m => m.Value == false)); - } - } - - public Dictionary Matches { get; set; } - } -} diff --git a/src/NzbDrone.Core/CustomFormats/FormatTagMatchesGroup.cs b/src/NzbDrone.Core/CustomFormats/FormatTagMatchesGroup.cs new file mode 100644 index 000000000..98652214c --- /dev/null +++ b/src/NzbDrone.Core/CustomFormats/FormatTagMatchesGroup.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Core.CustomFormats +{ + public class FormatTagMatchesGroup + { + public TagType Type { get; set; } + + public Dictionary Matches { get; set; } + + public bool DidMatch => !(Matches.Any(m => m.Key.TagModifier.HasFlag(TagModifier.AbsolutelyRequired) && m.Value == false) || + Matches.All(m => m.Value == false)); + } +} diff --git a/src/NzbDrone.Core/Datastore/Converters/CustomFormatIntConverter.cs b/src/NzbDrone.Core/Datastore/Converters/CustomFormatIntConverter.cs index 69db3e979..d5ca9d989 100644 --- a/src/NzbDrone.Core/Datastore/Converters/CustomFormatIntConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/CustomFormatIntConverter.cs @@ -1,52 +1,15 @@ using System; -using System.Data; using System.Text.Json; using System.Text.Json.Serialization; -using Dapper; -using NzbDrone.Common.Serializer; using NzbDrone.Core.CustomFormats; namespace NzbDrone.Core.Datastore.Converters { - public class DapperCustomFormatIntConverter : SqlMapper.TypeHandler - { - public override void SetValue(IDbDataParameter parameter, CustomFormat value) - { - parameter.Value = value.Id; - } - - public override CustomFormat Parse(object value) - { - Console.WriteLine(value.ToJson()); - - if (value is DBNull) - { - return null; - } - - var val = Convert.ToInt32(value); - - if (val == 0) - { - return CustomFormat.None; - } - - return CustomFormatService.AllCustomFormats[val]; - } - } - public class CustomFormatIntConverter : JsonConverter { public override CustomFormat Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var val = reader.GetInt32(); - - if (val == 0) - { - return CustomFormat.None; - } - - return CustomFormatService.AllCustomFormats[val]; + return new CustomFormat { Id = reader.GetInt32() }; } public override void Write(Utf8JsonWriter writer, CustomFormat value, JsonSerializerOptions options) diff --git a/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs b/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs index 3bbb0835d..af8e48fb3 100644 --- a/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs @@ -14,7 +14,7 @@ namespace NzbDrone.Core.Datastore.Converters var serializerSettings = new JsonSerializerOptions { AllowTrailingCommas = true, - IgnoreNullValues = false, + IgnoreNullValues = true, PropertyNameCaseInsensitive = true, DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, diff --git a/src/NzbDrone.Core/Datastore/Migration/165_remove_custom_formats_from_quality_model.cs b/src/NzbDrone.Core/Datastore/Migration/165_remove_custom_formats_from_quality_model.cs new file mode 100644 index 000000000..ee80c6dc8 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/165_remove_custom_formats_from_quality_model.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Dapper; +using FluentMigrator; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Converters; +using NzbDrone.Core.Datastore.Migration.Framework; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(165)] + public class remove_custom_formats_from_quality_model : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Blacklist").AddColumn("IndexerFlags").AsInt32().WithDefaultValue(0); + Alter.Table("MovieFiles").AddColumn("IndexerFlags").AsInt32().WithDefaultValue(0); + + // Switch Quality and Language to int in pending releases, remove custom formats + Execute.WithConnection(FixPendingReleases); + + // Remove Custom Formats from QualityModel + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter()); + Execute.WithConnection((conn, tran) => RemoveCustomFormatFromQuality(conn, tran, "Blacklist")); + Execute.WithConnection((conn, tran) => RemoveCustomFormatFromQuality(conn, tran, "History")); + Execute.WithConnection((conn, tran) => RemoveCustomFormatFromQuality(conn, tran, "MovieFiles")); + + // Fish out indexer flags from history + Execute.WithConnection(AddIndexerFlagsToBlacklist); + Execute.WithConnection(AddIndexerFlagsToMovieFiles); + } + + private void FixPendingReleases(IDbConnection conn, IDbTransaction tran) + { + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter()); + var rows = conn.Query("SELECT Id, ParsedMovieInfo from PendingReleases"); + + var newRows = new List(); + + foreach (var row in rows) + { + var old = row.ParsedMovieInfo; + + var newQuality = new QualityModel165 + { + Quality = old.Quality.Quality.Id, + Revision = old.Quality.Revision, + HardcodedSubs = old.Quality.HardcodedSubs + }; + + var languages = old.Languages?.Select(x => (Language)x).Select(x => x.Id).ToList(); + + var correct = new ParsedMovieInfo165 + { + MovieTitle = old.MovieTitle, + SimpleReleaseTitle = old.SimpleReleaseTitle, + Quality = newQuality, + Languages = languages, + ReleaseGroup = old.ReleaseGroup, + ReleaseHash = old.ReleaseHash, + Edition = old.Edition, + Year = old.Year, + ImdbId = old.ImdbId + }; + + newRows.Add(new ParsedMovieInfoData165 + { + Id = row.Id, + ParsedMovieInfo = correct + }); + } + + var sql = $"UPDATE PendingReleases SET ParsedMovieInfo = @ParsedMovieInfo WHERE Id = @Id"; + + conn.Execute(sql, newRows, transaction: tran); + } + + private void RemoveCustomFormatFromQuality(IDbConnection conn, IDbTransaction tran, string table) + { + var rows = conn.Query($"SELECT Id, Quality from {table}"); + + var sql = $"UPDATE {table} SET Quality = @Quality WHERE Id = @Id"; + + conn.Execute(sql, rows, transaction: tran); + } + + private void AddIndexerFlagsToBlacklist(IDbConnection conn, IDbTransaction tran) + { + var blacklists = conn.Query("SELECT Blacklist.Id, Blacklist.TorrentInfoHash, History.Data " + + "FROM Blacklist " + + "JOIN History ON Blacklist.MovieId = History.MovieId " + + "WHERE History.EventType = 1"); + + var toUpdate = new List(); + + foreach (var item in blacklists) + { + var dict = Json.Deserialize>(item.Data); + + if (dict.GetValueOrDefault("torrentInfoHash") == item.TorrentInfoHash && + Enum.TryParse(dict.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags)) + { + if (flags != 0) + { + toUpdate.Add(new IndexerFlagsItem + { + Id = item.Id, + IndexerFlags = (int)flags + }); + } + } + } + + var updateSql = "UPDATE Blacklist SET IndexerFlags = @IndexerFlags WHERE Id = @Id"; + conn.Execute(updateSql, toUpdate, transaction: tran); + } + + private void AddIndexerFlagsToMovieFiles(IDbConnection conn, IDbTransaction tran) + { + var movieFiles = conn.Query("SELECT MovieFiles.Id, MovieFiles.SceneName, History.SourceTitle, History.Data " + + "FROM MovieFiles " + + "JOIN History ON MovieFiles.MovieId = History.MovieId " + + "WHERE History.EventType = 1"); + + var toUpdate = new List(); + + foreach (var item in movieFiles) + { + var dict = Json.Deserialize>(item.Data); + + if (item.SourceTitle == item.SceneName && + Enum.TryParse(dict.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags)) + { + if (flags != 0) + { + toUpdate.Add(new IndexerFlagsItem + { + Id = item.Id, + IndexerFlags = (int)flags + }); + } + } + } + + var updateSql = "UPDATE MovieFiles SET IndexerFlags = @IndexerFlags WHERE Id = @Id"; + conn.Execute(updateSql, toUpdate, transaction: tran); + } + + private class ParsedMovieInfoData164 : ModelBase + { + public ParsedMovieInfo164 ParsedMovieInfo { get; set; } + } + + private class ParsedMovieInfo164 + { + public string MovieTitle { get; set; } + public string SimpleReleaseTitle { get; set; } + public QualityModel164 Quality { get; set; } + public List Languages { get; set; } + public string ReleaseGroup { get; set; } + public string ReleaseHash { get; set; } + public string Edition { get; set; } + public int Year { get; set; } + public string ImdbId { get; set; } + } + + private class QualityModel164 + { + public Quality164 Quality { get; set; } + public Revision165 Revision { get; set; } + public string HardcodedSubs { get; set; } + } + + private class Quality164 + { + public int Id { get; set; } + } + + private class ParsedMovieInfoData165 : ModelBase + { + public ParsedMovieInfo165 ParsedMovieInfo { get; set; } + } + + private class ParsedMovieInfo165 + { + public string MovieTitle { get; set; } + public string SimpleReleaseTitle { get; set; } + public QualityModel165 Quality { get; set; } + public List Languages { get; set; } + public string ReleaseGroup { get; set; } + public string ReleaseHash { get; set; } + public string Edition { get; set; } + public int Year { get; set; } + public string ImdbId { get; set; } + } + + private class BlacklistData : ModelBase + { + public string TorrentInfoHash { get; set; } + public string Data { get; set; } + } + + private class MovieFileData : ModelBase + { + public string SceneName { get; set; } + public string SourceTitle { get; set; } + public string Data { get; set; } + } + + private class IndexerFlagsItem : ModelBase + { + public int IndexerFlags { get; set; } + } + + private class QualityRow : ModelBase + { + public QualityModel165 Quality { get; set; } + } + + private class QualityModel165 + { + public int Quality { get; set; } + public Revision165 Revision { get; set; } + public string HardcodedSubs { get; set; } + } + + private class Revision165 + { + public int Version { get; set; } + public int Real { get; set; } + public bool IsRepack { get; set; } + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 11f029df4..8290918fa 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -111,7 +111,7 @@ namespace NzbDrone.Core.Datastore .Ignore(d => d.GroupName) .Ignore(d => d.Weight); - Mapper.Entity("CustomFormats").RegisterModel(); + Mapper.Entity("CustomFormats").RegisterModel(); Mapper.Entity("Profiles").RegisterModel(); Mapper.Entity("Logs").RegisterModel(); @@ -147,11 +147,10 @@ namespace NzbDrone.Core.Datastore SqlMapper.RemoveTypeMap(typeof(DateTime)); SqlMapper.AddTypeHandler(new DapperUtcConverter()); SqlMapper.AddTypeHandler(new DapperQualityIntConverter()); - SqlMapper.AddTypeHandler(new DapperCustomFormatIntConverter()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new QualityIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new CustomFormatIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new QualityTagStringConverter())); - SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter(new CustomFormatIntConverter(), new QualityIntConverter())); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter(new QualityIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); @@ -160,7 +159,7 @@ namespace NzbDrone.Core.Datastore SqlMapper.AddTypeHandler(new DapperLanguageIntConverter()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new LanguageIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); - SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter(new QualityIntConverter(), new LanguageIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); SqlMapper.AddTypeHandler(new OsPathConverter()); diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs index e5dcac0f4..78c3c10b5 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs @@ -2,11 +2,9 @@ using System; using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Configuration; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles.Delay; -using NzbDrone.Core.Qualities; namespace NzbDrone.Core.DecisionEngine { @@ -49,12 +47,6 @@ namespace NzbDrone.Core.DecisionEngine return leftValue.CompareTo(rightValue); } - private int CompareByReverse(TSubject left, TSubject right, Func funcValue) - where TValue : IComparable - { - return CompareBy(left, right, funcValue) * -1; - } - private int CompareAll(params int[] comparers) { return comparers.Select(comparer => comparer).FirstOrDefault(result => result != 0); @@ -63,23 +55,9 @@ namespace NzbDrone.Core.DecisionEngine private int CompareQuality(DownloadDecision x, DownloadDecision y) { return CompareAll(CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.Movie.Profile.GetIndex(remoteMovie.ParsedMovieInfo.Quality.Quality)), - CompareCustomFormats(x, y), - CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.ParsedMovieInfo.Quality.Revision.Real), - CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.ParsedMovieInfo.Quality.Revision.Version)); - } - - private int CompareCustomFormats(DownloadDecision x, DownloadDecision y) - { - var left = x.RemoteMovie.ParsedMovieInfo.Quality.CustomFormats.WithNone(); - var right = y.RemoteMovie.ParsedMovieInfo.Quality.CustomFormats; - - var leftIndicies = QualityModelComparer.GetIndicies(left, x.RemoteMovie.Movie.Profile); - var rightIndicies = QualityModelComparer.GetIndicies(right, y.RemoteMovie.Movie.Profile); - - var leftTotal = leftIndicies.Sum(); - var rightTotal = rightIndicies.Sum(); - - return leftTotal.CompareTo(rightTotal); + CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.Movie.Profile.GetIndices(remoteMovie.CustomFormats).Select(i => Math.Pow(2, i)).Sum()), + CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.ParsedMovieInfo.Quality.Revision.Real), + CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.ParsedMovieInfo.Quality.Revision.Version)); } private int ComparePreferredWords(DownloadDecision x, DownloadDecision y) diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index ab4cbeead..2b7883938 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -6,6 +6,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Serializer; using NzbDrone.Core.Configuration; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Languages; @@ -26,19 +27,19 @@ namespace NzbDrone.Core.DecisionEngine private readonly IEnumerable _specifications; private readonly IParsingService _parsingService; private readonly IConfigService _configService; - private readonly IQualityDefinitionService _definitionService; + private readonly ICustomFormatCalculationService _formatCalculator; private readonly Logger _logger; public DownloadDecisionMaker(IEnumerable specifications, - IParsingService parsingService, - IConfigService configService, - IQualityDefinitionService qualityDefinitionService, - Logger logger) + IParsingService parsingService, + IConfigService configService, + ICustomFormatCalculationService formatCalculator, + Logger logger) { _specifications = specifications; _parsingService = parsingService; _configService = configService; - _definitionService = qualityDefinitionService; + _formatCalculator = formatCalculator; _logger = logger; } @@ -106,7 +107,7 @@ namespace NzbDrone.Core.DecisionEngine result.ReleaseName = report.Title; var remoteMovie = result.RemoteMovie; - + remoteMovie.CustomFormats = _formatCalculator.ParseCustomFormat(parsedMovieInfo); remoteMovie.Release = report; remoteMovie.MappingResult = result.MappingResultType; diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CustomFormatAllowedByProfileSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CustomFormatAllowedByProfileSpecification.cs index 5a5dff4b7..5cd6260c4 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CustomFormatAllowedByProfileSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CustomFormatAllowedByProfileSpecification.cs @@ -1,5 +1,7 @@ +using System.Collections.Generic; using System.Linq; using NLog; +using NzbDrone.Common.Extensions; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -20,14 +22,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) { - var formats = subject.ParsedMovieInfo.Quality.CustomFormats.WithNone(); - _logger.Debug("Checking if report meets custom format requirements. {0}", formats.ToExtendedString()); + var formats = subject.CustomFormats.Any() ? subject.CustomFormats : new List { CustomFormat.None }; + _logger.Debug("Checking if report meets custom format requirements. {0}", formats.ConcatToString()); var notAllowedFormats = subject.Movie.Profile.FormatItems.Where(v => v.Allowed == false).Select(f => f.Format).ToList(); var notWantedFormats = notAllowedFormats.Intersect(formats); if (notWantedFormats.Any()) { - _logger.Debug("Custom Formats {0} rejected by Movie's profile", notWantedFormats.ToExtendedString()); - return Decision.Reject("Custom Formats {0} not wanted in profile", notWantedFormats.ToExtendedString()); + _logger.Debug("Custom Formats {0} rejected by Movie's profile", notWantedFormats.ConcatToString()); + return Decision.Reject("Custom Formats {0} not wanted in profile", notWantedFormats.ConcatToString()); } return Decision.Accept(); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs index cfa51c120..8f6ff73dc 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs @@ -1,4 +1,6 @@ using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -6,12 +8,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { public class CutoffSpecification : IDecisionEngineSpecification { - private readonly UpgradableSpecification _qualityUpgradableSpecification; + private readonly IUpgradableSpecification _upgradableSpecification; + private readonly ICustomFormatCalculationService _formatService; private readonly Logger _logger; - public CutoffSpecification(UpgradableSpecification qualityUpgradableSpecification, Logger logger) + public CutoffSpecification(IUpgradableSpecification upgradableSpecification, + ICustomFormatCalculationService formatService, + Logger logger) { - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; + _formatService = formatService; _logger = logger; } @@ -21,17 +27,25 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) { var profile = subject.Movie.Profile; + var file = subject.Movie.MovieFile; - if (subject.Movie.MovieFile != null) + if (file != null) { - if (!_qualityUpgradableSpecification.CutoffNotMet(profile, - subject.Movie.MovieFile.Quality, - subject.ParsedMovieInfo.Quality)) + file.Movie = subject.Movie; + var customFormats = _formatService.ParseCustomFormat(file); + + if (!_upgradableSpecification.CutoffNotMet(profile, + file.Quality, + customFormats, + subject.ParsedMovieInfo.Quality)) { + _logger.Debug("Existing custom formats {0} meet cutoff", + customFormats.ConcatToString()); + var qualityCutoffIndex = profile.GetIndex(profile.Cutoff); var qualityCutoff = profile.Items[qualityCutoffIndex.Index]; - return Decision.Reject("Existing file meets cutoff: {0} - {1}", qualityCutoff, subject.Movie.Profile.Cutoff); + return Decision.Reject("Existing file meets cutoff: {0}", qualityCutoff); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs index 85d00592a..b65e6ee25 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs @@ -1,5 +1,7 @@ using System.Linq; using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Queue; @@ -9,15 +11,18 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public class QueueSpecification : IDecisionEngineSpecification { private readonly IQueueService _queueService; - private readonly UpgradableSpecification _qualityUpgradableSpecification; + private readonly UpgradableSpecification _upgradableSpecification; + private readonly ICustomFormatCalculationService _formatService; private readonly Logger _logger; public QueueSpecification(IQueueService queueService, - UpgradableSpecification qualityUpgradableSpecification, - Logger logger) + UpgradableSpecification upgradableSpecification, + ICustomFormatCalculationService formatService, + Logger logger) { _queueService = queueService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; + _formatService = formatService; _logger = logger; } @@ -36,25 +41,38 @@ namespace NzbDrone.Core.DecisionEngine.Specifications var remoteMovie = queueItem.RemoteMovie; var qualityProfile = subject.Movie.Profile; - _logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteMovie.ParsedMovieInfo.Quality); + var customFormats = _formatService.ParseCustomFormat(remoteMovie.ParsedMovieInfo); - if (!_qualityUpgradableSpecification.CutoffNotMet(qualityProfile, remoteMovie.ParsedMovieInfo.Quality, subject.ParsedMovieInfo.Quality)) + _logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0} - {1}", + remoteMovie.ParsedMovieInfo.Quality, + customFormats.ConcatToString()); + + if (!_upgradableSpecification.CutoffNotMet(qualityProfile, + remoteMovie.ParsedMovieInfo.Quality, + customFormats, + subject.ParsedMovieInfo.Quality)) { return Decision.Reject("Quality for release in queue already meets cutoff: {0}", remoteMovie.ParsedMovieInfo.Quality); } _logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0}", remoteMovie.ParsedMovieInfo.Quality); - if (!_qualityUpgradableSpecification.IsUpgradable(qualityProfile, remoteMovie.ParsedMovieInfo.Quality, subject.ParsedMovieInfo.Quality)) + if (!_upgradableSpecification.IsUpgradable(qualityProfile, + remoteMovie.ParsedMovieInfo.Quality, + remoteMovie.CustomFormats, + subject.ParsedMovieInfo.Quality, + subject.CustomFormats)) { return Decision.Reject("Quality for release in queue is of equal or higher preference: {0}", remoteMovie.ParsedMovieInfo.Quality); } _logger.Debug("Checking if profiles allow upgrading. Queued: {0}", remoteMovie.ParsedMovieInfo.Quality); - if (!_qualityUpgradableSpecification.IsUpgradeAllowed(subject.Movie.Profile, + if (!_upgradableSpecification.IsUpgradeAllowed(subject.Movie.Profile, remoteMovie.ParsedMovieInfo.Quality, - subject.ParsedMovieInfo.Quality)) + remoteMovie.CustomFormats, + subject.ParsedMovieInfo.Quality, + subject.CustomFormats)) { return Decision.Reject("Another release is queued and the Quality profile does not allow upgrades"); } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs index c9e826d13..e748b2d64 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs @@ -1,5 +1,6 @@ using System.Linq; using NLog; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Download.Pending; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -12,16 +13,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync { private readonly IPendingReleaseService _pendingReleaseService; private readonly IUpgradableSpecification _qualityUpgradableSpecification; + private readonly ICustomFormatCalculationService _formatService; private readonly IDelayProfileService _delayProfileService; private readonly Logger _logger; public DelaySpecification(IPendingReleaseService pendingReleaseService, IUpgradableSpecification qualityUpgradableSpecification, + ICustomFormatCalculationService formatService, IDelayProfileService delayProfileService, Logger logger) { _pendingReleaseService = pendingReleaseService; _qualityUpgradableSpecification = qualityUpgradableSpecification; + _formatService = formatService; _delayProfileService = delayProfileService; _logger = logger; } @@ -65,9 +69,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync var comparer = new QualityModelComparer(profile); - if (isPreferredProtocol && (subject.Movie.MovieFileId != 0 && subject.Movie.MovieFile != null) && (preferredCount > 0 || preferredWords == null)) + var file = subject.Movie.MovieFile; + + if (isPreferredProtocol && (subject.Movie.MovieFileId != 0 && file != null) && (preferredCount > 0 || preferredWords == null)) { - var upgradable = _qualityUpgradableSpecification.IsUpgradable(profile, subject.Movie.MovieFile.Quality, subject.ParsedMovieInfo.Quality); + var customFormats = _formatService.ParseCustomFormat(file); + var upgradable = _qualityUpgradableSpecification.IsUpgradable(profile, + file.Quality, + customFormats, + subject.ParsedMovieInfo.Quality, + subject.CustomFormats); if (upgradable) { diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs index eefe79c6d..921404978 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs @@ -2,6 +2,7 @@ using System; using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.History; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -11,17 +12,20 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync public class HistorySpecification : IDecisionEngineSpecification { private readonly IHistoryService _historyService; - private readonly UpgradableSpecification _qualityUpgradableSpecification; + private readonly UpgradableSpecification _upgradableSpecification; + private readonly ICustomFormatCalculationService _formatService; private readonly IConfigService _configService; private readonly Logger _logger; public HistorySpecification(IHistoryService historyService, - UpgradableSpecification qualityUpgradableSpecification, - IConfigService configService, - Logger logger) + UpgradableSpecification upgradableSpecification, + ICustomFormatCalculationService formatService, + IConfigService configService, + Logger logger) { _historyService = historyService; - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; + _formatService = formatService; _configService = configService; _logger = logger; } @@ -45,10 +49,20 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed) { - var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12)); - var cutoffUnmet = _qualityUpgradableSpecification.CutoffNotMet(subject.Movie.Profile, mostRecent.Quality, subject.ParsedMovieInfo.Quality); - var upgradeable = _qualityUpgradableSpecification.IsUpgradable(subject.Movie.Profile, mostRecent.Quality, subject.ParsedMovieInfo.Quality); + var customFormats = _formatService.ParseCustomFormat(mostRecent); + + var cutoffUnmet = _upgradableSpecification.CutoffNotMet(subject.Movie.Profile, + mostRecent.Quality, + customFormats, + subject.ParsedMovieInfo.Quality); + var upgradeable = _upgradableSpecification.IsUpgradable(subject.Movie.Profile, + mostRecent.Quality, + customFormats, + subject.ParsedMovieInfo.Quality, + subject.CustomFormats); + + var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12)); if (!recent && cdhEnabled) { return Decision.Accept(); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs index dcedb23db..385c4ecf2 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs @@ -1,5 +1,9 @@ +using System.Collections.Generic; +using System.Linq; using NLog; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; @@ -7,10 +11,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { public interface IUpgradableSpecification { - bool IsUpgradable(Profile profile, QualityModel currentQuality, QualityModel newQuality = null); - bool CutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null); + bool IsUpgradable(Profile profile, QualityModel currentQuality, List currentCustomFormats, QualityModel newQuality, List newCustomFormats); + bool CutoffNotMet(Profile profile, QualityModel currentQuality, List currentFormats, QualityModel newQuality = null); + bool QualityCutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null); bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality); - bool IsUpgradeAllowed(Profile qualityProfile, QualityModel currentQuality, QualityModel newQuality); + bool IsUpgradeAllowed(Profile qualityProfile, QualityModel currentQuality, List currentCustomFormats, QualityModel newQuality, List newCustomFormats); } public class UpgradableSpecification : IUpgradableSpecification @@ -24,43 +29,69 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } - public bool IsUpgradable(Profile profile, QualityModel currentQuality, QualityModel newQuality = null) + public bool IsUpgradable(Profile profile, QualityModel currentQuality, List currentCustomFormats, QualityModel newQuality, List newCustomFormats) { - if (newQuality != null) + var qualityComparer = new QualityModelComparer(profile); + var qualityCompare = qualityComparer.Compare(newQuality?.Quality, currentQuality.Quality); + + if (qualityCompare > 0) + { + _logger.Debug("New item has a better quality"); + return true; + } + + if (qualityCompare < 0) + { + _logger.Debug("Existing item has better quality, skipping"); + return false; + } + + // Accept unless the user doesn't want to prefer propers, optionally they can + // use preferred words to prefer propers/repacks over non-propers/repacks. + if (_configService.AutoDownloadPropers && + newQuality?.Revision.CompareTo(currentQuality.Revision) > 0) + { + _logger.Debug("New item has a better quality revision"); + return true; + } + + var customFormatCompare = new CustomFormatsComparer(profile).Compare(newCustomFormats, currentCustomFormats); + + if (customFormatCompare <= 0) { - int compare = new QualityModelComparer(profile).Compare(newQuality, currentQuality); - if (compare <= 0) - { - return false; - } - - if (IsRevisionUpgrade(currentQuality, newQuality)) - { - _logger.Debug("New item has a better quality revision"); - return true; - } + _logger.Debug("New item's custom formats [{0}] do not improve on [{1}], skipping", + newCustomFormats.ConcatToString(), + currentCustomFormats.ConcatToString()); + return false; } - _logger.Debug("New item has a better quality"); + _logger.Debug("New item has a custom format upgrade"); return true; } - public bool CutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null) + public bool QualityCutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null) { - var comparer = new QualityModelComparer(profile); - var cutoffCompare = comparer.Compare(currentQuality.Quality.Id, profile.Cutoff); + var cutoffCompare = new QualityModelComparer(profile).Compare(currentQuality.Quality.Id, profile.Cutoff); if (cutoffCompare < 0) { return true; } - if (comparer.Compare(currentQuality.CustomFormats, profile.FormatCutoff) < 0) + if (newQuality != null && IsRevisionUpgrade(currentQuality, newQuality)) { return true; } - if (newQuality != null && IsRevisionUpgrade(currentQuality, newQuality)) + return false; + } + + private bool CustomFormatCutoffNotMet(Profile profile, List currentFormats) + { + var cutoff = new List { profile.FormatItems.Single(x => x.Format.Id == profile.FormatCutoff).Format }; + var cutoffCompare = new CustomFormatsComparer(profile).Compare(currentFormats, cutoff); + + if (cutoffCompare < 0) { return true; } @@ -68,6 +99,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return false; } + public bool CutoffNotMet(Profile profile, QualityModel currentQuality, List currentFormats, QualityModel newQuality = null) + { + return QualityCutoffNotMet(profile, currentQuality, newQuality) || CustomFormatCutoffNotMet(profile, currentFormats); + } + public bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality) { var compare = newQuality.Revision.CompareTo(currentQuality.Revision); @@ -81,11 +117,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return false; } - public bool IsUpgradeAllowed(Profile qualityProfile, QualityModel currentQuality, QualityModel newQuality) + public bool IsUpgradeAllowed(Profile qualityProfile, QualityModel currentQuality, List currentCustomFormats, QualityModel newQuality, List newCustomFormats) { var isQualityUpgrade = new QualityModelComparer(qualityProfile).Compare(newQuality, currentQuality) > 0; + var isCustomFormatUpgrade = new CustomFormatsComparer(qualityProfile).Compare(newCustomFormats, currentCustomFormats) > 0; - if (isQualityUpgrade && qualityProfile.UpgradeAllowed) + if ((isQualityUpgrade || isCustomFormatUpgrade) && qualityProfile.UpgradeAllowed) { _logger.Debug("Quality profile allows upgrading"); return true; diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs index 14784effb..d0e9181e2 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs @@ -1,4 +1,6 @@ using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -7,11 +9,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public class UpgradeAllowedSpecification : IDecisionEngineSpecification { private readonly UpgradableSpecification _upgradableSpecification; + private readonly ICustomFormatCalculationService _formatService; private readonly Logger _logger; - public UpgradeAllowedSpecification(UpgradableSpecification upgradableSpecification, Logger logger) + public UpgradeAllowedSpecification(UpgradableSpecification upgradableSpecification, + ICustomFormatCalculationService formatService, + Logger logger) { _upgradableSpecification = upgradableSpecification; + _formatService = formatService; _logger = logger; } @@ -32,11 +38,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } - _logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality); + file.Movie = subject.Movie; + var customFormats = _formatService.ParseCustomFormat(file); + _logger.Debug("Comparing file quality with report. Existing file is {0} [{1}]", file.Quality, customFormats.ConcatToString()); if (!_upgradableSpecification.IsUpgradeAllowed(qualityProfile, file.Quality, - subject.ParsedMovieInfo.Quality)) + customFormats, + subject.ParsedMovieInfo.Quality, + subject.CustomFormats)) { _logger.Debug("Upgrading is not allowed by the quality profile"); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs index 8dfb6eae7..1f299c6fe 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs @@ -1,4 +1,6 @@ using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; @@ -7,11 +9,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public class UpgradeDiskSpecification : IDecisionEngineSpecification { private readonly UpgradableSpecification _qualityUpgradableSpecification; + private readonly ICustomFormatCalculationService _formatService; private readonly Logger _logger; - public UpgradeDiskSpecification(UpgradableSpecification qualityUpgradableSpecification, Logger logger) + public UpgradeDiskSpecification(UpgradableSpecification qualityUpgradableSpecification, + ICustomFormatCalculationService formatService, + Logger logger) { _qualityUpgradableSpecification = qualityUpgradableSpecification; + _formatService = formatService; _logger = logger; } @@ -25,12 +31,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return Decision.Accept(); } + var profile = subject.Movie.Profile; var file = subject.Movie.MovieFile; - _logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality); + file.Movie = subject.Movie; + var customFormats = _formatService.ParseCustomFormat(file); + _logger.Debug("Comparing file quality with report. Existing file is {0} [{1}]", file.Quality, customFormats.ConcatToString()); - if (!_qualityUpgradableSpecification.IsUpgradable(subject.Movie.Profile, file.Quality, subject.ParsedMovieInfo.Quality)) + if (!_qualityUpgradableSpecification.IsUpgradable(profile, + file.Quality, + customFormats, + subject.ParsedMovieInfo.Quality, + subject.CustomFormats)) { - return Decision.Reject("Quality for existing file on disk is of equal or higher preference: {0}", file.Quality); + return Decision.Reject("Quality for existing file on disk is of equal or higher preference: {0} [{1}]", file.Quality, customFormats.ConcatToString()); } return Decision.Accept(); diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs index 8478713ca..99223dc2a 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs @@ -5,6 +5,7 @@ using NLog; using NzbDrone.Common.Crypto; using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Indexers; using NzbDrone.Core.Jobs; @@ -43,18 +44,20 @@ namespace NzbDrone.Core.Download.Pending private readonly IDelayProfileService _delayProfileService; private readonly ITaskManager _taskManager; private readonly IConfigService _configService; + private readonly ICustomFormatCalculationService _formatCalculator; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; public PendingReleaseService(IIndexerStatusService indexerStatusService, - IPendingReleaseRepository repository, - IMovieService movieService, - IParsingService parsingService, - IDelayProfileService delayProfileService, - ITaskManager taskManager, - IConfigService configService, - IEventAggregator eventAggregator, - Logger logger) + IPendingReleaseRepository repository, + IMovieService movieService, + IParsingService parsingService, + IDelayProfileService delayProfileService, + ITaskManager taskManager, + IConfigService configService, + ICustomFormatCalculationService formatCalculator, + IEventAggregator eventAggregator, + Logger logger) { _indexerStatusService = indexerStatusService; _repository = repository; @@ -63,6 +66,7 @@ namespace NzbDrone.Core.Download.Pending _delayProfileService = delayProfileService; _taskManager = taskManager; _configService = configService; + _formatCalculator = formatCalculator; _eventAggregator = eventAggregator; _logger = logger; } @@ -159,6 +163,8 @@ namespace NzbDrone.Core.Download.Pending { if (pendingRelease.RemoteMovie != null) { + pendingRelease.RemoteMovie.CustomFormats = _formatCalculator.ParseCustomFormat(pendingRelease.ParsedMovieInfo); + var ect = pendingRelease.Release.PublishDate.AddMinutes(GetDelay(pendingRelease.RemoteMovie)); if (ect < nextRssSync.Value) diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs index 49d86fea4..572c5b863 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs @@ -5,6 +5,7 @@ using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.History; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; @@ -27,20 +28,23 @@ namespace NzbDrone.Core.Download.TrackedDownloads private readonly IHistoryService _historyService; private readonly IEventAggregator _eventAggregator; private readonly IConfigService _config; + private readonly ICustomFormatCalculationService _formatCalculator; private readonly Logger _logger; private readonly ICached _cache; public TrackedDownloadService(IParsingService parsingService, - ICacheManager cacheManager, - IHistoryService historyService, - IConfigService config, - IEventAggregator eventAggregator, - Logger logger) + ICacheManager cacheManager, + IHistoryService historyService, + IConfigService config, + ICustomFormatCalculationService formatCalculator, + IEventAggregator eventAggregator, + Logger logger) { _parsingService = parsingService; _historyService = historyService; _cache = cacheManager.GetCache(GetType()); _config = config; + _formatCalculator = formatCalculator; _eventAggregator = eventAggregator; _logger = logger; } @@ -129,6 +133,12 @@ namespace NzbDrone.Core.Download.TrackedDownloads } } + // Calculate custom formats + if (trackedDownload.RemoteMovie != null) + { + trackedDownload.RemoteMovie.CustomFormats = _formatCalculator.ParseCustomFormat(parsedMovieInfo); + } + // Track it so it can be displayed in the queue even though we can't determine which movie it is for if (trackedDownload.RemoteMovie == null) { diff --git a/src/NzbDrone.Core/MediaFiles/Commands/UpdateMovieFileQualityCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/UpdateMovieFileQualityCommand.cs deleted file mode 100644 index 678d39574..000000000 --- a/src/NzbDrone.Core/MediaFiles/Commands/UpdateMovieFileQualityCommand.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.MediaFiles.Commands -{ - public class UpdateMovieFileQualityCommand : Command - { - public IEnumerable MovieFileIds { get; set; } - - public override bool SendUpdatesToClient => true; - - public UpdateMovieFileQualityCommand(IEnumerable movieFileIds) - { - MovieFileIds = movieFileIds; - } - } -} diff --git a/src/NzbDrone.Core/MediaFiles/MovieFile.cs b/src/NzbDrone.Core/MediaFiles/MovieFile.cs index c6ee6dc8f..6b7bc88b5 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieFile.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieFile.cs @@ -5,6 +5,7 @@ using NzbDrone.Core.Datastore; using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.Movies; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.MediaFiles @@ -18,6 +19,7 @@ namespace NzbDrone.Core.MediaFiles public DateTime DateAdded { get; set; } public string SceneName { get; set; } public string ReleaseGroup { get; set; } + public IndexerFlags IndexerFlags { get; set; } public QualityModel Quality { get; set; } public List Languages { get; set; } public MediaInfoModel MediaInfo { get; set; } diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQuality.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQuality.cs index 3962c579c..31b862fa8 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQuality.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/AggregateQuality.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using NLog; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; @@ -33,7 +32,6 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators var modifier = Modifier.NONE; var modifierConfidence = Confidence.Default; var revison = new Revision(); - var customFormats = new List(); foreach (var augmentedQuality in augmentedQualities) { @@ -62,18 +60,11 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators { revison = augmentedQuality.Revision; } - - if (augmentedQuality.CustomFormats != null) - { - var newFormats = augmentedQuality.CustomFormats.Where(c => !customFormats.Any(p => p.Id == c.Id)); - - customFormats.AddRange(newFormats); - } } _logger.Trace("Finding quality. Source: {0}. Resolution: {1}. Modifier {2}", source, resolution, modifier); - var quality = new QualityModel(QualityFinder.FindBySourceAndResolution(source, resolution, modifier), revison, customFormats); + var quality = new QualityModel(QualityFinder.FindBySourceAndResolution(source, resolution, modifier), revison); if (resolutionConfidence == Confidence.MediaInfo) { diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromDownloadClientItem.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromDownloadClientItem.cs index 1d3dd3a37..09f02fe6b 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromDownloadClientItem.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromDownloadClientItem.cs @@ -19,8 +19,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenter Confidence.Tag, quality.Quality.Modifier, Confidence.Tag, - quality.Revision, - quality.CustomFormats); + quality.Revision); } } } diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFileName.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFileName.cs index 4636d359b..e45db6f80 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFileName.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFileName.cs @@ -24,8 +24,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenter confidence, quality.Quality.Modifier, confidence, - quality.Revision, - quality.CustomFormats); + quality.Revision); } } } diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFolder.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFolder.cs index 6604cc2a2..574212c20 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFolder.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityFromFolder.cs @@ -19,8 +19,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenter Confidence.Tag, quality.Quality.Modifier, Confidence.Tag, - quality.Revision, - quality.CustomFormats); + quality.Revision); } } } diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityResult.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityResult.cs index b1ed51ab8..b8b9f841b 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityResult.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Aggregation/Aggregators/Augmenters/Quality/AugmentQualityResult.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality @@ -14,16 +12,13 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenter public Confidence ModifierConfidence { get; set; } public Revision Revision { get; set; } - public List CustomFormats { get; set; } - public AugmentQualityResult(Source source, Confidence sourceConfidence, int resolution, Confidence resolutionConfidence, Modifier modifier, Confidence modifierConfidence, - Revision revision, - List customFormats) + Revision revision) { Source = source; SourceConfidence = sourceConfidence; @@ -32,22 +27,21 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenter Modifier = modifier; ModifierConfidence = modifierConfidence; Revision = revision; - CustomFormats = customFormats; } public static AugmentQualityResult SourceOnly(Source source, Confidence sourceConfidence) { - return new AugmentQualityResult(source, sourceConfidence, 0, Confidence.Default, Modifier.NONE, Confidence.Default, null, null); + return new AugmentQualityResult(source, sourceConfidence, 0, Confidence.Default, Modifier.NONE, Confidence.Default, null); } public static AugmentQualityResult ResolutionOnly(int resolution, Confidence resolutionConfidence) { - return new AugmentQualityResult(Source.UNKNOWN, Confidence.Default, resolution, resolutionConfidence, Modifier.NONE, Confidence.Default, null, null); + return new AugmentQualityResult(Source.UNKNOWN, Confidence.Default, resolution, resolutionConfidence, Modifier.NONE, Confidence.Default, null); } public static AugmentQualityResult ModifierOnly(Modifier modifier, Confidence modifierConfidence) { - return new AugmentQualityResult(Source.UNKNOWN, Confidence.Default, 0, Confidence.Default, modifier, modifierConfidence, null, null); + return new AugmentQualityResult(Source.UNKNOWN, Confidence.Default, 0, Confidence.Default, modifier, modifierConfidence, null); } } } diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs index a75423ae6..ee949ded8 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/ImportApprovedMovie.cs @@ -7,6 +7,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Core.Download; using NzbDrone.Core.Extras; +using NzbDrone.Core.History; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; @@ -26,20 +27,23 @@ namespace NzbDrone.Core.MediaFiles.MovieImport private readonly IMediaFileService _mediaFileService; private readonly IExtraService _extraService; private readonly IDiskProvider _diskProvider; + private readonly IHistoryService _historyService; private readonly IEventAggregator _eventAggregator; private readonly Logger _logger; public ImportApprovedMovie(IUpgradeMediaFiles movieFileUpgrader, - IMediaFileService mediaFileService, - IExtraService extraService, - IDiskProvider diskProvider, - IEventAggregator eventAggregator, - Logger logger) + IMediaFileService mediaFileService, + IExtraService extraService, + IDiskProvider diskProvider, + IHistoryService historyService, + IEventAggregator eventAggregator, + Logger logger) { _movieFileUpgrader = movieFileUpgrader; _mediaFileService = mediaFileService; _extraService = extraService; _diskProvider = diskProvider; + _historyService = historyService; _eventAggregator = eventAggregator; _logger = logger; } @@ -85,6 +89,18 @@ namespace NzbDrone.Core.MediaFiles.MovieImport movieFile.ReleaseGroup = localMovie.ReleaseGroup; movieFile.Edition = localMovie.Edition; + if (downloadClientItem?.DownloadId.IsNotNullOrWhiteSpace() == true) + { + var grabHistory = _historyService.FindByDownloadId(downloadClientItem.DownloadId) + .OrderByDescending(h => h.Date) + .FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed); + + if (Enum.TryParse(grabHistory?.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags)) + { + movieFile.IndexerFlags = flags; + } + } + bool copyOnly; switch (importMode) { diff --git a/src/NzbDrone.Core/MediaFiles/UpdateMovieFileQualityService.cs b/src/NzbDrone.Core/MediaFiles/UpdateMovieFileQualityService.cs deleted file mode 100644 index 11078be8b..000000000 --- a/src/NzbDrone.Core/MediaFiles/UpdateMovieFileQualityService.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NLog; -using NzbDrone.Common.Extensions; -using NzbDrone.Common.Instrumentation.Extensions; -using NzbDrone.Core.History; -using NzbDrone.Core.MediaFiles.Commands; -using NzbDrone.Core.MediaFiles.Events; -using NzbDrone.Core.Messaging.Commands; -using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.MediaFiles -{ - public interface IUpdateMovieFileQualityService - { - } - - public class UpdateMovieFileQualityService : IUpdateMovieFileQualityService, IExecute - { - private readonly IMediaFileService _mediaFileService; - private readonly IHistoryService _historyService; - private readonly IParsingService _parsingService; - private readonly IEventAggregator _eventAggregator; - private readonly Logger _logger; - - public UpdateMovieFileQualityService(IMediaFileService mediaFileService, - IHistoryService historyService, - IParsingService parsingService, - IEventAggregator eventAggregator, - Logger logger) - { - _mediaFileService = mediaFileService; - _historyService = historyService; - _parsingService = parsingService; - _eventAggregator = eventAggregator; - _logger = logger; - } - - //TODO add some good tests for this! - public void Execute(UpdateMovieFileQualityCommand command) - { - var movieFiles = _mediaFileService.GetMovies(command.MovieFileIds); - - var count = 1; - - foreach (var movieFile in movieFiles) - { - _logger.ProgressInfo("Updating quality for {0}/{1} files.", count, movieFiles.Count); - - var history = _historyService.GetByMovieId(movieFile.MovieId, null).OrderByDescending(h => h.Date); - var latestImported = history.FirstOrDefault(h => h.EventType == HistoryEventType.DownloadFolderImported); - var latestImportedName = latestImported?.SourceTitle; - var latestGrabbed = history.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed); - var sizeMovie = new LocalMovie(); - sizeMovie.Size = movieFile.Size; - - var helpers = new List { sizeMovie }; - - if (movieFile.MediaInfo != null) - { - helpers.Add(movieFile.MediaInfo); - } - - if (latestGrabbed != null) - { - helpers.Add(latestGrabbed); - } - - ParsedMovieInfo parsedMovieInfo = null; - - if (latestImportedName?.IsNotNullOrWhiteSpace() == true) - { - parsedMovieInfo = _parsingService.ParseMovieInfo(latestImportedName, helpers); - } - - if (parsedMovieInfo == null) - { - _logger.Debug("Could not parse movie info from history source title, using current path instead: {0}.", movieFile.RelativePath); - parsedMovieInfo = _parsingService.ParseMovieInfo(movieFile.RelativePath, helpers); - } - - //Only update Custom formats for now. - if (parsedMovieInfo != null) - { - movieFile.Quality.CustomFormats = parsedMovieInfo.Quality.CustomFormats; - _mediaFileService.Update(movieFile); - _eventAggregator.PublishEvent(new MovieFileUpdatedEvent(movieFile)); - } - else - { - _logger.Warn("Could not update custom formats for {0}, since it's title could not be parsed!", movieFile); - } - - count++; - } - } - } -} diff --git a/src/NzbDrone.Core/Parser/Augmenters/AugmentWithAdditionalFormats.cs b/src/NzbDrone.Core/Parser/Augmenters/AugmentWithAdditionalFormats.cs deleted file mode 100644 index 54528815a..000000000 --- a/src/NzbDrone.Core/Parser/Augmenters/AugmentWithAdditionalFormats.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.CustomFormats; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.Parser.Augmenters -{ - public class AugmentWithAdditionalFormats : IAugmentParsedMovieInfo - { - public Type HelperType - { - get - { - return typeof(CustomFormat); - } - } - - public ParsedMovieInfo AugmentMovieInfo(ParsedMovieInfo movieInfo, object helper) - { - if (helper is CustomFormat format) - { - if (movieInfo.ExtraInfo.GetValueOrDefault("AdditionalFormats") is List existing) - { - existing.Add(format); - movieInfo.ExtraInfo["AdditionalFormats"] = existing; - } - else - { - movieInfo.ExtraInfo["AdditionalFormats"] = new List { format }; - } - } - - return movieInfo; - } - } -} diff --git a/src/NzbDrone.Core/Parser/Augmenters/AugmentWithParsedMovieInfo.cs b/src/NzbDrone.Core/Parser/Augmenters/AugmentWithParsedMovieInfo.cs index e5d7954a2..a709e95ff 100644 --- a/src/NzbDrone.Core/Parser/Augmenters/AugmentWithParsedMovieInfo.cs +++ b/src/NzbDrone.Core/Parser/Augmenters/AugmentWithParsedMovieInfo.cs @@ -30,12 +30,6 @@ namespace NzbDrone.Core.Parser.Augmenters movieInfo.Edition = otherInfo.Edition; } - if (otherInfo.Quality != null) - { - movieInfo.Quality.CustomFormats = movieInfo.Quality.CustomFormats.Union(otherInfo.Quality.CustomFormats) - .Distinct().ToList(); - } - if (otherInfo.ReleaseGroup.IsNotNullOrWhiteSpace() && movieInfo.ReleaseGroup.IsNullOrWhiteSpace()) { movieInfo.ReleaseGroup = otherInfo.ReleaseGroup; diff --git a/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs index a9dbc2c18..2fafb840d 100644 --- a/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ParsedMovieInfo.cs @@ -10,14 +10,14 @@ namespace NzbDrone.Core.Parser.Model public string MovieTitle { get; set; } public string SimpleReleaseTitle { get; set; } public QualityModel Quality { get; set; } - [JsonIgnore] - public Dictionary ExtraInfo = new Dictionary(); - public List Languages = new List(); + public List Languages { get; set; } = new List(); public string ReleaseGroup { get; set; } public string ReleaseHash { get; set; } public string Edition { get; set; } public int Year { get; set; } public string ImdbId { get; set; } + [JsonIgnore] + public Dictionary ExtraInfo { get; set; } = new Dictionary(); public override string ToString() { diff --git a/src/NzbDrone.Core/Parser/Model/RemoteMovie.cs b/src/NzbDrone.Core/Parser/Model/RemoteMovie.cs index 9274fcaff..d11985550 100644 --- a/src/NzbDrone.Core/Parser/Model/RemoteMovie.cs +++ b/src/NzbDrone.Core/Parser/Model/RemoteMovie.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Download.Clients; using NzbDrone.Core.Movies; @@ -7,6 +9,7 @@ namespace NzbDrone.Core.Parser.Model { public ReleaseInfo Release { get; set; } public ParsedMovieInfo ParsedMovieInfo { get; set; } + public List CustomFormats { get; set; } public Movie Movie { get; set; } public MappingResultType MappingResult { get; set; } public bool DownloadAllowed { get; set; } diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index c73eb5b38..f56819024 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -385,7 +385,7 @@ namespace NzbDrone.Core.Parser return ReplaceGermanUmlauts(NormalizeRegex.Replace(title, string.Empty).ToLower()).RemoveAccent(); } - public static string NormalizeEpisodeTitle(string title) + public static string NormalizeEpisodeTitle(this string title) { title = SpecialEpisodeWordRegex.Replace(title, string.Empty); title = PunctuationRegex.Replace(title, " "); @@ -395,7 +395,7 @@ namespace NzbDrone.Core.Parser .ToLower(); } - public static string NormalizeTitle(string title) + public static string NormalizeTitle(this string title) { title = WordDelimiterRegex.Replace(title, " "); title = PunctuationRegex.Replace(title, string.Empty); @@ -406,6 +406,11 @@ namespace NzbDrone.Core.Parser return title.Trim().ToLower(); } + public static string SimplifyReleaseTitle(this string title) + { + return SimpleReleaseTitleRegex.Replace(title, string.Empty); + } + public static string ParseReleaseGroup(string title) { title = title.Trim(); diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 2eac5bde0..aefef6e2b 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -3,9 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using NLog; -using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Movies; @@ -13,7 +11,6 @@ using NzbDrone.Core.Movies.AlternativeTitles; using NzbDrone.Core.Parser.Augmenters; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.RomanNumerals; -using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Parser { @@ -25,31 +22,24 @@ namespace NzbDrone.Core.Parser ParsedMovieInfo EnhanceMovieInfo(ParsedMovieInfo parsedMovieInfo, List helpers = null); ParsedMovieInfo ParseMinimalMovieInfo(string path, bool isDir = false); ParsedMovieInfo ParseMinimalPathMovieInfo(string path); - List MatchFormatTags(ParsedMovieInfo movieInfo); } public class ParsingService : IParsingService { + private static HashSet _arabicRomanNumeralMappings; + private readonly IMovieService _movieService; private readonly IConfigService _config; - private readonly IQualityDefinitionService _qualityDefinitionService; - private readonly ICustomFormatService _formatService; private readonly IEnumerable _augmenters; private readonly Logger _logger; - private static HashSet _arabicRomanNumeralMappings; - public ParsingService( - IMovieService movieService, + public ParsingService(IMovieService movieService, IConfigService configService, - IQualityDefinitionService qualityDefinitionService, - ICustomFormatService formatService, IEnumerable augmenters, Logger logger) { _movieService = movieService; _config = configService; - _qualityDefinitionService = qualityDefinitionService; - _formatService = formatService; _augmenters = augmenters; _logger = logger; @@ -80,15 +70,6 @@ namespace NzbDrone.Core.Parser minimalInfo = AugmentMovieInfo(minimalInfo, helpers); } - // minimalInfo.Quality.Quality = QualityFinder.FindBySourceAndResolution(minimalInfo.Quality.Quality.Source, minimalInfo.Quality.Quality.Resolution, - // minimalInfo.Quality.Quality.Modifier); - if (minimalInfo != null) - { - minimalInfo.Quality.CustomFormats = ParseCustomFormat(minimalInfo); - - _logger.Debug("Quality parsed: {0}", minimalInfo.Quality); - } - return minimalInfo; } @@ -105,41 +86,6 @@ namespace NzbDrone.Core.Parser return minimalInfo; } - private List ParseCustomFormat(ParsedMovieInfo movieInfo) - { - var matches = MatchFormatTags(movieInfo); - var goodMatches = matches.Where(m => m.GoodMatch); - return goodMatches.Select(r => r.CustomFormat).ToList(); - } - - public List MatchFormatTags(ParsedMovieInfo movieInfo) - { - var formats = _formatService.All(); - - if (movieInfo.ExtraInfo.GetValueOrDefault("AdditionalFormats") is List additionalFormats) - { - formats.AddRange(additionalFormats); - } - - var matches = new List(); - - foreach (var customFormat in formats) - { - var formatMatches = customFormat.FormatTags.GroupBy(t => t.TagType).Select(g => - new FormatTagMatchesGroup(g.Key, g.ToList().ToDictionary(t => t, t => t.DoesItMatch(movieInfo)))); - - var formatTagMatchesGroups = formatMatches.ToList(); - matches.Add(new FormatTagMatchResult - { - CustomFormat = customFormat, - GroupMatches = formatTagMatchesGroups, - GoodMatch = formatTagMatchesGroups.All(g => g.DidMatch) - }); - } - - return matches; - } - public ParsedMovieInfo ParseMinimalMovieInfo(string file, bool isDir = false) { return Parser.ParseMovieTitle(file, _config.ParsingLeniency > 0, isDir); diff --git a/src/NzbDrone.Core/Profiles/Profile.cs b/src/NzbDrone.Core/Profiles/Profile.cs index cb23f7c28..69ab194cb 100644 --- a/src/NzbDrone.Core/Profiles/Profile.cs +++ b/src/NzbDrone.Core/Profiles/Profile.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using NzbDrone.Core.Languages; using NzbDrone.Core.Qualities; @@ -73,5 +74,11 @@ namespace NzbDrone.Core.Profiles return new QualityIndex(); } + + public List GetIndices(List formats) + { + var allFormats = formats.Any() ? formats : new List { CustomFormat.None }; + return allFormats.Select(f => FormatItems.FindIndex(v => Equals(v.Format, f))).ToList(); + } } } diff --git a/src/NzbDrone.Core/Profiles/ProfileRepository.cs b/src/NzbDrone.Core/Profiles/ProfileRepository.cs index b50d15ed8..717bb43d8 100644 --- a/src/NzbDrone.Core/Profiles/ProfileRepository.cs +++ b/src/NzbDrone.Core/Profiles/ProfileRepository.cs @@ -1,3 +1,7 @@ +using System.Collections.Generic; +using System.Linq; +using Dapper; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; @@ -10,9 +14,34 @@ namespace NzbDrone.Core.Profiles public class ProfileRepository : BasicRepository, IProfileRepository { - public ProfileRepository(IMainDatabase database, IEventAggregator eventAggregator) + private readonly ICustomFormatService _customFormatService; + + public ProfileRepository(IMainDatabase database, + IEventAggregator eventAggregator, + ICustomFormatService customFormatService) : base(database, eventAggregator) { + _customFormatService = customFormatService; + } + + protected override IEnumerable GetResults(SqlBuilder.Template sql) + { + var cfs = _customFormatService.All().ToDictionary(c => c.Id); + + var profiles = base.GetResults(sql); + + // Do the conversions from Id to full CustomFormat object here instead of in + // CustomFormatIntConverter to remove need to for a static property containing + // all the custom formats + foreach (var profile in profiles) + { + foreach (var formatItem in profile.FormatItems) + { + formatItem.Format = formatItem.Format.Id == 0 ? CustomFormat.None : cfs[formatItem.Format.Id]; + } + } + + return profiles; } public bool Exists(int id) diff --git a/src/NzbDrone.Core/Profiles/ProfileService.cs b/src/NzbDrone.Core/Profiles/ProfileService.cs index 80be98574..7d6e6a018 100644 --- a/src/NzbDrone.Core/Profiles/ProfileService.cs +++ b/src/NzbDrone.Core/Profiles/ProfileService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.CustomFormats.Events; using NzbDrone.Core.Languages; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Events; @@ -15,8 +16,6 @@ namespace NzbDrone.Core.Profiles { Profile Add(Profile profile); void Update(Profile profile); - void AddCustomFormat(CustomFormat format); - void DeleteCustomFormat(int formatId); void Delete(int id); List All(); Profile Get(int id); @@ -24,24 +23,27 @@ namespace NzbDrone.Core.Profiles Profile GetDefaultProfile(string name, Quality cutoff = null, params Quality[] allowed); } - public class ProfileService : IProfileService, IHandle + public class ProfileService : IProfileService, + IHandle, + IHandle, + IHandle { private readonly IProfileRepository _profileRepository; + private readonly ICustomFormatService _formatService; private readonly IMovieService _movieService; private readonly INetImportFactory _netImportFactory; - private readonly ICustomFormatService _formatService; private readonly Logger _logger; public ProfileService(IProfileRepository profileRepository, - IMovieService movieService, - INetImportFactory netImportFactory, - ICustomFormatService formatService, - Logger logger) + ICustomFormatService formatService, + IMovieService movieService, + INetImportFactory netImportFactory, + Logger logger) { _profileRepository = profileRepository; + _formatService = formatService; _movieService = movieService; _netImportFactory = netImportFactory; - _formatService = formatService; _logger = logger; } @@ -55,36 +57,6 @@ namespace NzbDrone.Core.Profiles _profileRepository.Update(profile); } - public void AddCustomFormat(CustomFormat customFormat) - { - var all = All(); - foreach (var profile in all) - { - profile.FormatItems.Add(new ProfileFormatItem - { - Allowed = true, - Format = customFormat - }); - - Update(profile); - } - } - - public void DeleteCustomFormat(int formatId) - { - var all = All(); - foreach (var profile in all) - { - profile.FormatItems = profile.FormatItems.Where(c => c.Format.Id != formatId).ToList(); - if (profile.FormatCutoff == formatId) - { - profile.FormatCutoff = CustomFormat.None.Id; - } - - Update(profile); - } - } - public void Delete(int id) { if (_movieService.GetAllMovies().Any(c => c.ProfileId == id) || _netImportFactory.All().Any(c => c.ProfileId == id)) @@ -110,10 +82,38 @@ namespace NzbDrone.Core.Profiles return _profileRepository.Exists(id); } + public void Handle(CustomFormatAddedEvent message) + { + var all = All(); + foreach (var profile in all) + { + profile.FormatItems.Add(new ProfileFormatItem + { + Allowed = true, + Format = message.CustomFormat + }); + + Update(profile); + } + } + + public void Handle(CustomFormatDeletedEvent message) + { + var all = All(); + foreach (var profile in all) + { + profile.FormatItems = profile.FormatItems.Where(c => c.Format.Id != message.CustomFormat.Id).ToList(); + if (profile.FormatCutoff == message.CustomFormat.Id) + { + profile.FormatCutoff = CustomFormat.None.Id; + } + + Update(profile); + } + } + public void Handle(ApplicationStartedEvent message) { - // Hack to force custom formats to be loaded into memory, if you have a better solution please let me know. - _formatService.All(); if (All().Any()) { return; @@ -207,9 +207,7 @@ namespace NzbDrone.Core.Profiles public Profile GetDefaultProfile(string name, Quality cutoff = null, params Quality[] allowed) { var groupedQualites = Quality.DefaultQualityDefinitions.GroupBy(q => q.Weight); - var formats = _formatService.All(); var items = new List(); - var formatItems = new List(); var groupId = 1000; var profileCutoff = cutoff == null ? Quality.Unknown.Id : cutoff.Id; @@ -245,15 +243,20 @@ namespace NzbDrone.Core.Profiles groupId++; } - foreach (var format in formats) + var formatItems = new List { - formatItems.Add(new ProfileFormatItem + new ProfileFormatItem { - Id = format.Id, - Format = format, - Allowed = false - }); - } + Id = 0, + Allowed = true, + Format = CustomFormat.None + } + }.Concat(_formatService.All().Select(format => new ProfileFormatItem + { + Id = format.Id, + Allowed = false, + Format = format + })).ToList(); var qualityProfile = new Profile { @@ -262,19 +265,9 @@ namespace NzbDrone.Core.Profiles Items = items, Language = Language.English, FormatCutoff = CustomFormat.None.Id, - FormatItems = new List - { - new ProfileFormatItem - { - Id = 0, - Allowed = true, - Format = CustomFormat.None - } - } + FormatItems = formatItems }; - qualityProfile.FormatItems.AddRange(formatItems); - return qualityProfile; } diff --git a/src/NzbDrone.Core/Qualities/Modifier.cs b/src/NzbDrone.Core/Qualities/Modifier.cs new file mode 100644 index 000000000..37f8a3061 --- /dev/null +++ b/src/NzbDrone.Core/Qualities/Modifier.cs @@ -0,0 +1,12 @@ +namespace NzbDrone.Core.Qualities +{ + public enum Modifier + { + NONE = 0, + REGIONAL, + SCREENER, + RAWHD, + BRDISK, + REMUX + } +} diff --git a/src/NzbDrone.Core/Qualities/QualityModel.cs b/src/NzbDrone.Core/Qualities/QualityModel.cs index b9ba426c3..c8dcfa0bb 100644 --- a/src/NzbDrone.Core/Qualities/QualityModel.cs +++ b/src/NzbDrone.Core/Qualities/QualityModel.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using Newtonsoft.Json; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Qualities @@ -10,8 +8,6 @@ namespace NzbDrone.Core.Qualities { public Quality Quality { get; set; } - public List CustomFormats { get; set; } - public Revision Revision { get; set; } public string HardcodedSubs { get; set; } @@ -24,16 +20,15 @@ namespace NzbDrone.Core.Qualities { } - public QualityModel(Quality quality, Revision revision = null, List customFormats = null) + public QualityModel(Quality quality, Revision revision = null) { Quality = quality; Revision = revision ?? new Revision(); - CustomFormats = customFormats ?? new List(); } public override string ToString() { - return string.Format("{0} {1} ({2})", Quality, Revision, CustomFormats.WithNone().ToExtendedString()); + return string.Format("{0} {1}", Quality, Revision); } public override int GetHashCode() diff --git a/src/NzbDrone.Core/Qualities/QualityModelComparer.cs b/src/NzbDrone.Core/Qualities/QualityModelComparer.cs index a08bb1968..ac407b2d8 100644 --- a/src/NzbDrone.Core/Qualities/QualityModelComparer.cs +++ b/src/NzbDrone.Core/Qualities/QualityModelComparer.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; -using System.Linq; using NzbDrone.Common.EnsureThat; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Profiles; namespace NzbDrone.Core.Qualities { - public class QualityModelComparer : IComparer, IComparer, IComparer, IComparer> + public class QualityModelComparer : IComparer, IComparer { private readonly Profile _profile; @@ -50,49 +48,10 @@ namespace NzbDrone.Core.Qualities if (result == 0) { - result = Compare(left.CustomFormats, right.CustomFormats); - - if (result == 0) - { - result = left.Revision.CompareTo(right.Revision); - } + result = left.Revision.CompareTo(right.Revision); } return result; } - - public int Compare(List left, List right) - { - List leftIndicies = GetIndicies(left, _profile); - List rightIndicies = GetIndicies(right, _profile); - - int leftTotal = leftIndicies.Sum(); - int rightTotal = rightIndicies.Sum(); - - return leftTotal.CompareTo(rightTotal); - } - - public static List GetIndicies(List formats, Profile profile) - { - return formats.WithNone().Select(f => profile.FormatItems.FindIndex(v => Equals(v.Format, f))).ToList(); - } - - public int Compare(CustomFormat left, CustomFormat right) - { - var leftIndex = _profile.FormatItems.FindIndex(v => Equals(v.Format, left)); - var rightIndex = _profile.FormatItems.FindIndex(v => Equals(v.Format, right)); - - return leftIndex.CompareTo(rightIndex); - } - - public int Compare(List left, int right) - { - left = left.WithNone(); - - var leftIndicies = GetIndicies(left, _profile); - var rightIndex = _profile.FormatItems.FindIndex(v => Equals(v.Format.Id, right)); - - return leftIndicies.Select(i => i.CompareTo(rightIndex)).Max(); - } } } diff --git a/src/NzbDrone.Core/Qualities/Source.cs b/src/NzbDrone.Core/Qualities/Source.cs new file mode 100644 index 000000000..6035390a6 --- /dev/null +++ b/src/NzbDrone.Core/Qualities/Source.cs @@ -0,0 +1,16 @@ +namespace NzbDrone.Core.Qualities +{ + public enum Source + { + UNKNOWN = 0, + CAM, + TELESYNC, + TELECINE, + WORKPRINT, + DVD, + TV, + WEBDL, + WEBRIP, + BLURAY + } +} diff --git a/src/NzbDrone.Host.Test/ContainerFixture.cs b/src/NzbDrone.Host.Test/ContainerFixture.cs index 7db0bf259..8a7f8e021 100644 --- a/src/NzbDrone.Host.Test/ContainerFixture.cs +++ b/src/NzbDrone.Host.Test/ContainerFixture.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using NzbDrone.Common; using NzbDrone.Common.Composition; using NzbDrone.Common.EnvironmentInfo; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using NzbDrone.Core.Download; using NzbDrone.Core.Download.TrackedDownloads; @@ -38,11 +37,6 @@ namespace NzbDrone.App.Test // set up a dummy broadcaster to allow tests to resolve var mockBroadcaster = new Mock(); _container.Register(mockBroadcaster.Object); - - // A dummy custom format repository since this isn't a DB test - var mockCustomFormat = Mocker.GetMock(); - mockCustomFormat.Setup(x => x.All()).Returns(new List()); - _container.Register(mockCustomFormat.Object); } [Test] diff --git a/src/Radarr.Api.V3/Blacklist/BlacklistModule.cs b/src/Radarr.Api.V3/Blacklist/BlacklistModule.cs index 86137503d..30078d564 100644 --- a/src/Radarr.Api.V3/Blacklist/BlacklistModule.cs +++ b/src/Radarr.Api.V3/Blacklist/BlacklistModule.cs @@ -1,4 +1,5 @@ using NzbDrone.Core.Blacklisting; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using Radarr.Http; @@ -7,10 +8,14 @@ namespace Radarr.Api.V3.Blacklist public class BlacklistModule : RadarrRestModule { private readonly IBlacklistService _blacklistService; + private readonly ICustomFormatCalculationService _formatCalculator; - public BlacklistModule(IBlacklistService blacklistService) + public BlacklistModule(IBlacklistService blacklistService, + ICustomFormatCalculationService formatCalculator) { _blacklistService = blacklistService; + _formatCalculator = formatCalculator; + GetResourcePaged = GetBlacklist; DeleteResource = DeleteBlacklist; } @@ -19,7 +24,7 @@ namespace Radarr.Api.V3.Blacklist { var pagingSpec = pagingResource.MapToPagingSpec("date", SortDirection.Descending); - return ApplyToPage(_blacklistService.Paged, pagingSpec, BlacklistResourceMapper.MapToResource); + return ApplyToPage(_blacklistService.Paged, pagingSpec, (blacklist) => BlacklistResourceMapper.MapToResource(blacklist, _formatCalculator)); } private void DeleteBlacklist(int id) diff --git a/src/Radarr.Api.V3/Blacklist/BlacklistResource.cs b/src/Radarr.Api.V3/Blacklist/BlacklistResource.cs index 9cef0409e..b0c95f93a 100644 --- a/src/Radarr.Api.V3/Blacklist/BlacklistResource.cs +++ b/src/Radarr.Api.V3/Blacklist/BlacklistResource.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Indexers; using NzbDrone.Core.Languages; using NzbDrone.Core.Qualities; +using Radarr.Api.V3.CustomFormats; using Radarr.Api.V3.Movies; using Radarr.Http.REST; @@ -14,6 +16,7 @@ namespace Radarr.Api.V3.Blacklist public string SourceTitle { get; set; } public List Languages { get; set; } public QualityModel Quality { get; set; } + public List CustomFormats { get; set; } public DateTime Date { get; set; } public DownloadProtocol Protocol { get; set; } public string Indexer { get; set; } @@ -24,7 +27,7 @@ namespace Radarr.Api.V3.Blacklist public static class BlacklistResourceMapper { - public static BlacklistResource MapToResource(this NzbDrone.Core.Blacklisting.Blacklist model) + public static BlacklistResource MapToResource(this NzbDrone.Core.Blacklisting.Blacklist model, ICustomFormatCalculationService formatCalculator) { if (model == null) { @@ -39,6 +42,7 @@ namespace Radarr.Api.V3.Blacklist SourceTitle = model.SourceTitle, Languages = model.Languages, Quality = model.Quality, + CustomFormats = formatCalculator.ParseCustomFormat(model).ToResource(), Date = model.Date, Protocol = model.Protocol, Indexer = model.Indexer, diff --git a/src/Radarr.Api.V3/Qualities/CustomFormatModule.cs b/src/Radarr.Api.V3/CustomFormats/CustomFormatModule.cs similarity index 84% rename from src/Radarr.Api.V3/Qualities/CustomFormatModule.cs rename to src/Radarr.Api.V3/CustomFormats/CustomFormatModule.cs index b7c185dde..a20fad06e 100644 --- a/src/Radarr.Api.V3/Qualities/CustomFormatModule.cs +++ b/src/Radarr.Api.V3/CustomFormats/CustomFormatModule.cs @@ -6,16 +6,20 @@ using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Parser; using Radarr.Http; -namespace Radarr.Api.V3.Qualities +namespace Radarr.Api.V3.CustomFormats { public class CustomFormatModule : RadarrRestModule { private readonly ICustomFormatService _formatService; + private readonly ICustomFormatCalculationService _formatCalculator; private readonly IParsingService _parsingService; - public CustomFormatModule(ICustomFormatService formatService, IParsingService parsingService) + public CustomFormatModule(ICustomFormatService formatService, + ICustomFormatCalculationService formatCalculator, + IParsingService parsingService) { _formatService = formatService; + _formatCalculator = formatCalculator; _parsingService = parsingService; SharedValidator.RuleFor(c => c.Name).NotEmpty(); @@ -31,7 +35,7 @@ namespace Radarr.Api.V3.Qualities var allNewTags = c.Split(',').Select(t => t.ToLower()); var enumerable = allTags.ToList(); var newTags = allNewTags.ToList(); - return enumerable.All(newTags.Contains) && f.Id != v.Id && enumerable.Count() == newTags.Count(); + return enumerable.All(newTags.Contains) && f.Id != v.Id && enumerable.Count == newTags.Count; }); }) .WithMessage("Should be unique."); @@ -103,8 +107,8 @@ namespace Radarr.Api.V3.Qualities return new CustomFormatTestResource { - Matches = _parsingService.MatchFormatTags(parsed).ToResource(), - MatchedFormats = parsed.Quality.CustomFormats.ToResource() + Matches = _formatCalculator.MatchFormatTags(parsed).ToResource(), + MatchedFormats = _formatCalculator.ParseCustomFormat(parsed).ToResource() }; } @@ -125,8 +129,8 @@ namespace Radarr.Api.V3.Qualities return new CustomFormatTestResource { - Matches = _parsingService.MatchFormatTags(parsed).ToResource(), - MatchedFormats = parsed.Quality.CustomFormats.ToResource() + Matches = _formatCalculator.MatchFormatTags(parsed).ToResource(), + MatchedFormats = _formatCalculator.ParseCustomFormat(parsed).ToResource() }; } } diff --git a/src/Radarr.Api.V3/Qualities/CustomFormatResource.cs b/src/Radarr.Api.V3/CustomFormats/CustomFormatResource.cs similarity index 79% rename from src/Radarr.Api.V3/Qualities/CustomFormatResource.cs rename to src/Radarr.Api.V3/CustomFormats/CustomFormatResource.cs index a7ba6b78c..c1d489868 100644 --- a/src/Radarr.Api.V3/Qualities/CustomFormatResource.cs +++ b/src/Radarr.Api.V3/CustomFormats/CustomFormatResource.cs @@ -1,12 +1,15 @@ using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using NzbDrone.Core.CustomFormats; using Radarr.Http.REST; -namespace Radarr.Api.V3.Qualities +namespace Radarr.Api.V3.CustomFormats { public class CustomFormatResource : RestResource { + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)] + public override int Id { get; set; } public string Name { get; set; } public string FormatTags { get; set; } public string Simplicity { get; set; } @@ -24,8 +27,18 @@ namespace Radarr.Api.V3.Qualities }; } + public static List ToResource(this IEnumerable models) + { + return models.Select(m => m.ToResource()).ToList(); + } + public static CustomFormat ToModel(this CustomFormatResource resource) { + if (resource.Id == 0 && resource.Name == "None") + { + return CustomFormat.None; + } + return new CustomFormat { Id = resource.Id, @@ -33,10 +46,5 @@ namespace Radarr.Api.V3.Qualities FormatTags = resource.FormatTags.Split(',').Select(s => new FormatTag(s)).ToList() }; } - - public static List ToResource(this IEnumerable models) - { - return models.Select(m => m.ToResource()).ToList(); - } } } diff --git a/src/Radarr.Api.V3/Qualities/FormatTagMatchResultResource.cs b/src/Radarr.Api.V3/CustomFormats/FormatTagMatchResultResource.cs similarity index 78% rename from src/Radarr.Api.V3/Qualities/FormatTagMatchResultResource.cs rename to src/Radarr.Api.V3/CustomFormats/FormatTagMatchResultResource.cs index 9890a2235..0f8217b9a 100644 --- a/src/Radarr.Api.V3/Qualities/FormatTagMatchResultResource.cs +++ b/src/Radarr.Api.V3/CustomFormats/FormatTagMatchResultResource.cs @@ -4,9 +4,9 @@ using NzbDrone.Common.Extensions; using NzbDrone.Core.CustomFormats; using Radarr.Http.REST; -namespace Radarr.Api.V3.Qualities +namespace Radarr.Api.V3.CustomFormats { - public class FormatTagMatchResultResource : RestResource + public class CustomFormatMatchResultResource : RestResource { public CustomFormatResource CustomFormat { get; set; } public List GroupMatches { get; set; } @@ -21,27 +21,27 @@ namespace Radarr.Api.V3.Qualities public class CustomFormatTestResource : RestResource { - public List Matches { get; set; } + public List Matches { get; set; } public List MatchedFormats { get; set; } } public static class QualityTagMatchResultResourceMapper { - public static FormatTagMatchResultResource ToResource(this FormatTagMatchResult model) + public static CustomFormatMatchResultResource ToResource(this CustomFormatMatchResult model) { if (model == null) { return null; } - return new FormatTagMatchResultResource + return new CustomFormatMatchResultResource { CustomFormat = model.CustomFormat.ToResource(), GroupMatches = model.GroupMatches.ToResource() }; } - public static List ToResource(this IList models) + public static List ToResource(this IList models) { return models.Select(ToResource).ToList(); } diff --git a/src/Radarr.Api.V3/Qualities/FormatTagValidator.cs b/src/Radarr.Api.V3/CustomFormats/FormatTagValidator.cs similarity index 94% rename from src/Radarr.Api.V3/Qualities/FormatTagValidator.cs rename to src/Radarr.Api.V3/CustomFormats/FormatTagValidator.cs index bd0bcca36..3595769cc 100644 --- a/src/Radarr.Api.V3/Qualities/FormatTagValidator.cs +++ b/src/Radarr.Api.V3/CustomFormats/FormatTagValidator.cs @@ -3,7 +3,7 @@ using System.Linq; using FluentValidation.Validators; using NzbDrone.Core.CustomFormats; -namespace Radarr.Api.V3.Qualities +namespace Radarr.Api.V3.CustomFormats { public class FormatTagValidator : PropertyValidator { @@ -24,7 +24,7 @@ namespace Radarr.Api.V3.Qualities var invalidTags = tags.Where(t => !FormatTag.QualityTagRegex.IsMatch(t)); - if (invalidTags.Count() == 0) + if (!invalidTags.Any()) { return true; } diff --git a/src/Radarr.Api.V3/History/HistoryModule.cs b/src/Radarr.Api.V3/History/HistoryModule.cs index d7059bafd..6f7ecd084 100644 --- a/src/Radarr.Api.V3/History/HistoryModule.cs +++ b/src/Radarr.Api.V3/History/HistoryModule.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Generic; using System.Linq; using Nancy; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Download; using NzbDrone.Core.History; +using NzbDrone.Core.Movies; using Radarr.Api.V3.Movies; using Radarr.Http; using Radarr.Http.Extensions; @@ -16,14 +18,20 @@ namespace Radarr.Api.V3.History public class HistoryModule : RadarrRestModule { private readonly IHistoryService _historyService; + private readonly IMovieService _movieService; + private readonly ICustomFormatCalculationService _formatCalculator; private readonly IUpgradableSpecification _upgradableSpecification; private readonly IFailedDownloadService _failedDownloadService; public HistoryModule(IHistoryService historyService, + IMovieService movieService, + ICustomFormatCalculationService formatCalculator, IUpgradableSpecification upgradableSpecification, IFailedDownloadService failedDownloadService) { _historyService = historyService; + _movieService = movieService; + _formatCalculator = formatCalculator; _upgradableSpecification = upgradableSpecification; _failedDownloadService = failedDownloadService; GetResourcePaged = GetHistory; @@ -35,7 +43,12 @@ namespace Radarr.Api.V3.History protected HistoryResource MapToResource(NzbDrone.Core.History.History model, bool includeMovie) { - var resource = model.ToResource(); + if (model.Movie == null) + { + model.Movie = _movieService.GetMovie(model.MovieId); + } + + var resource = model.ToResource(_formatCalculator); if (includeMovie) { @@ -44,7 +57,7 @@ namespace Radarr.Api.V3.History if (model.Movie != null) { - resource.QualityCutoffNotMet = _upgradableSpecification.CutoffNotMet(model.Movie.Profile, model.Quality); + resource.QualityCutoffNotMet = _upgradableSpecification.QualityCutoffNotMet(model.Movie.Profile, model.Quality); } return resource; diff --git a/src/Radarr.Api.V3/History/HistoryResource.cs b/src/Radarr.Api.V3/History/HistoryResource.cs index a235e3554..50b499e1a 100644 --- a/src/Radarr.Api.V3/History/HistoryResource.cs +++ b/src/Radarr.Api.V3/History/HistoryResource.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.History; using NzbDrone.Core.Languages; using NzbDrone.Core.Qualities; +using Radarr.Api.V3.CustomFormats; using Radarr.Api.V3.Movies; using Radarr.Http.REST; @@ -14,6 +16,7 @@ namespace Radarr.Api.V3.History public string SourceTitle { get; set; } public List Languages { get; set; } public QualityModel Quality { get; set; } + public List CustomFormats { get; set; } public bool QualityCutoffNotMet { get; set; } public DateTime Date { get; set; } public string DownloadId { get; set; } @@ -27,7 +30,7 @@ namespace Radarr.Api.V3.History public static class HistoryResourceMapper { - public static HistoryResource ToResource(this NzbDrone.Core.History.History model) + public static HistoryResource ToResource(this NzbDrone.Core.History.History model, ICustomFormatCalculationService formatCalculator) { if (model == null) { @@ -42,6 +45,7 @@ namespace Radarr.Api.V3.History SourceTitle = model.SourceTitle, Languages = model.Languages, Quality = model.Quality, + CustomFormats = formatCalculator.ParseCustomFormat(model).ToResource(), //QualityCutoffNotMet Date = model.Date, diff --git a/src/Radarr.Api.V3/Indexers/ReleaseResource.cs b/src/Radarr.Api.V3/Indexers/ReleaseResource.cs index de44aea7c..ab634b37c 100644 --- a/src/Radarr.Api.V3/Indexers/ReleaseResource.cs +++ b/src/Radarr.Api.V3/Indexers/ReleaseResource.cs @@ -7,6 +7,7 @@ using NzbDrone.Core.Indexers; using NzbDrone.Core.Languages; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; +using Radarr.Api.V3.CustomFormats; using Radarr.Http.REST; namespace Radarr.Api.V3.Indexers @@ -15,6 +16,7 @@ namespace Radarr.Api.V3.Indexers { public string Guid { get; set; } public QualityModel Quality { get; set; } + public List CustomFormats { get; set; } public int QualityWeight { get; set; } public int Age { get; set; } public double AgeHours { get; set; } @@ -68,6 +70,7 @@ namespace Radarr.Api.V3.Indexers { Guid = releaseInfo.Guid, Quality = parsedMovieInfo.Quality, + CustomFormats = remoteMovie.CustomFormats.ToResource(), //QualityWeight Age = releaseInfo.Age, diff --git a/src/Radarr.Api.V3/MovieFiles/MovieFileModule.cs b/src/Radarr.Api.V3/MovieFiles/MovieFileModule.cs index 771f1944c..76633756c 100644 --- a/src/Radarr.Api.V3/MovieFiles/MovieFileModule.cs +++ b/src/Radarr.Api.V3/MovieFiles/MovieFileModule.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using Nancy; using NLog; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.MediaFiles; @@ -11,6 +12,7 @@ using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Movies; using NzbDrone.SignalR; +using Radarr.Api.V3.CustomFormats; using Radarr.Http; using Radarr.Http.Extensions; using BadRequestException = Radarr.Http.REST.BadRequestException; @@ -24,6 +26,7 @@ namespace Radarr.Api.V3.MovieFiles private readonly IMediaFileService _mediaFileService; private readonly IRecycleBinProvider _recycleBinProvider; private readonly IMovieService _movieService; + private readonly ICustomFormatCalculationService _formatCalculator; private readonly IUpgradableSpecification _qualityUpgradableSpecification; private readonly Logger _logger; @@ -31,6 +34,7 @@ namespace Radarr.Api.V3.MovieFiles IMediaFileService mediaFileService, IRecycleBinProvider recycleBinProvider, IMovieService movieService, + ICustomFormatCalculationService formatCalculator, IUpgradableSpecification qualityUpgradableSpecification, Logger logger) : base(signalRBroadcaster) @@ -38,6 +42,7 @@ namespace Radarr.Api.V3.MovieFiles _mediaFileService = mediaFileService; _recycleBinProvider = recycleBinProvider; _movieService = movieService; + _formatCalculator = formatCalculator; _qualityUpgradableSpecification = qualityUpgradableSpecification; _logger = logger; @@ -54,8 +59,11 @@ namespace Radarr.Api.V3.MovieFiles { var movieFile = _mediaFileService.GetMovie(id); var movie = _movieService.GetMovie(movieFile.MovieId); + movieFile.Movie = movie; - return movieFile.ToResource(movie, _qualityUpgradableSpecification); + var resource = movieFile.ToResource(movie, _qualityUpgradableSpecification); + resource.CustomFormats = _formatCalculator.ParseCustomFormat(movieFile).ToResource(); + return resource; } private List GetMovieFiles() @@ -72,8 +80,18 @@ namespace Radarr.Api.V3.MovieFiles { int movieId = Convert.ToInt32(movieIdQuery.Value); var movie = _movieService.GetMovie(movieId); + var file = _mediaFileService.GetFilesByMovie(movieId).FirstOrDefault(); - return _mediaFileService.GetFilesByMovie(movieId).ConvertAll(f => f.ToResource(movie, _qualityUpgradableSpecification)); + if (file == null) + { + return new List(); + } + + var resource = file.ToResource(movie, _qualityUpgradableSpecification); + file.Movie = movie; + resource.CustomFormats = _formatCalculator.ParseCustomFormat(file).ToResource(); + + return new List { resource }; } else { @@ -95,6 +113,7 @@ namespace Radarr.Api.V3.MovieFiles private void SetMovieFile(MovieFileResource movieFileResource) { var movieFile = _mediaFileService.GetMovie(movieFileResource.Id); + movieFile.IndexerFlags = movieFileResource.IndexerFlags; movieFile.Quality = movieFileResource.Quality; movieFile.Languages = movieFileResource.Languages; _mediaFileService.Update(movieFile); diff --git a/src/Radarr.Api.V3/MovieFiles/MovieFileResource.cs b/src/Radarr.Api.V3/MovieFiles/MovieFileResource.cs index f81c8a437..0ca0f43b2 100644 --- a/src/Radarr.Api.V3/MovieFiles/MovieFileResource.cs +++ b/src/Radarr.Api.V3/MovieFiles/MovieFileResource.cs @@ -4,7 +4,9 @@ using System.IO; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; +using Radarr.Api.V3.CustomFormats; using Radarr.Http.REST; namespace Radarr.Api.V3.MovieFiles @@ -17,7 +19,9 @@ namespace Radarr.Api.V3.MovieFiles public long Size { get; set; } public DateTime DateAdded { get; set; } public string SceneName { get; set; } + public IndexerFlags IndexerFlags { get; set; } public QualityModel Quality { get; set; } + public List CustomFormats { get; set; } public MediaInfoResource MediaInfo { get; set; } public bool QualityCutoffNotMet { get; set; } public List Languages { get; set; } @@ -43,6 +47,7 @@ namespace Radarr.Api.V3.MovieFiles Size = model.Size, DateAdded = model.DateAdded, SceneName = model.SceneName, + IndexerFlags = model.IndexerFlags, Quality = model.Quality, Languages = model.Languages, MediaInfo = model.MediaInfo.ToResource(model.SceneName), @@ -68,6 +73,7 @@ namespace Radarr.Api.V3.MovieFiles Size = model.Size, DateAdded = model.DateAdded, SceneName = model.SceneName, + IndexerFlags = model.IndexerFlags, Quality = model.Quality, Languages = model.Languages, MediaInfo = model.MediaInfo.ToResource(model.SceneName) @@ -91,10 +97,11 @@ namespace Radarr.Api.V3.MovieFiles Size = model.Size, DateAdded = model.DateAdded, SceneName = model.SceneName, + IndexerFlags = model.IndexerFlags, Quality = model.Quality, Languages = model.Languages, MediaInfo = model.MediaInfo.ToResource(model.SceneName), - QualityCutoffNotMet = upgradableSpecification.CutoffNotMet(movie.Profile, model.Quality) + QualityCutoffNotMet = upgradableSpecification.QualityCutoffNotMet(movie.Profile, model.Quality) }; } } diff --git a/src/Radarr.Api.V3/Profiles/Quality/QualityProfileResource.cs b/src/Radarr.Api.V3/Profiles/Quality/QualityProfileResource.cs index a6bda6d6e..3cba24d82 100644 --- a/src/Radarr.Api.V3/Profiles/Quality/QualityProfileResource.cs +++ b/src/Radarr.Api.V3/Profiles/Quality/QualityProfileResource.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Linq; -using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Languages; using NzbDrone.Core.Profiles; +using Radarr.Api.V3.CustomFormats; using Radarr.Http.REST; namespace Radarr.Api.V3.Profiles.Quality @@ -34,7 +34,7 @@ namespace Radarr.Api.V3.Profiles.Quality public class ProfileFormatItemResource : RestResource { - public CustomFormat Format { get; set; } + public CustomFormatResource Format { get; set; } public bool Allowed { get; set; } } @@ -82,7 +82,7 @@ namespace Radarr.Api.V3.Profiles.Quality { return new ProfileFormatItemResource { - Format = model.Format, + Format = model.Format.ToResource(), Allowed = model.Allowed }; } @@ -129,7 +129,7 @@ namespace Radarr.Api.V3.Profiles.Quality { return new ProfileFormatItem { - Format = resource.Format, + Format = resource.Format.ToModel(), Allowed = resource.Allowed }; } diff --git a/src/Radarr.Api.V3/Queue/QueueResource.cs b/src/Radarr.Api.V3/Queue/QueueResource.cs index 5cffcae51..1c0c52550 100644 --- a/src/Radarr.Api.V3/Queue/QueueResource.cs +++ b/src/Radarr.Api.V3/Queue/QueueResource.cs @@ -5,6 +5,7 @@ using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Indexers; using NzbDrone.Core.Languages; using NzbDrone.Core.Qualities; +using Radarr.Api.V3.CustomFormats; using Radarr.Api.V3.Movies; using Radarr.Http.REST; @@ -16,6 +17,7 @@ namespace Radarr.Api.V3.Queue public MovieResource Movie { get; set; } public List Languages { get; set; } public QualityModel Quality { get; set; } + public List CustomFormats { get; set; } public decimal Size { get; set; } public string Title { get; set; } public decimal Sizeleft { get; set; } @@ -48,6 +50,7 @@ namespace Radarr.Api.V3.Queue Movie = includeMovie && model.Movie != null ? model.Movie.ToResource() : null, Languages = model.Languages, Quality = model.Quality, + CustomFormats = model.RemoteMovie?.CustomFormats.ToResource(), Size = model.Size, Title = model.Title, Sizeleft = model.Sizeleft, diff --git a/src/Radarr.Http/REST/RestResource.cs b/src/Radarr.Http/REST/RestResource.cs index 8be1d2139..f3ce82b32 100644 --- a/src/Radarr.Http/REST/RestResource.cs +++ b/src/Radarr.Http/REST/RestResource.cs @@ -5,7 +5,7 @@ namespace Radarr.Http.REST public abstract class RestResource { [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public int Id { get; set; } + public virtual int Id { get; set; } [JsonIgnore] public virtual string ResourceName => GetType().Name.ToLowerInvariant().Replace("resource", "");