New: Calculate custom formats on demand

pull/4099/head
ta264 4 years ago
parent 13701498ce
commit df101258c5

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

@ -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 (
<TableRowCell key={name}>
<MovieFormats
formats={quality.customFormats}
formats={customFormats}
/>
</TableRowCell>
);
@ -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,

@ -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 (
<TableRowCell key={name}>
<MovieFormats
formats={quality.customFormats}
formats={customFormats}
/>
</TableRowCell>
);
@ -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,

@ -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 (
<TableRowCell key={name}>
<MovieFormats
formats={quality.customFormats}
formats={customFormats}
/>
</TableRowCell>
);
@ -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,

@ -113,6 +113,7 @@ class InteractiveSearchRow extends Component {
seeders,
leechers,
quality,
customFormats,
languages,
indexerFlags,
rejections,
@ -177,7 +178,7 @@ class InteractiveSearchRow extends Component {
<TableRowCell className={styles.customFormat}>
<MovieFormats
formats={quality.customFormats}
formats={customFormats}
/>
</TableRowCell>
@ -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,

@ -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 {
/>
</TableRowCell>
<TableRowCell key={name}>
<MovieFormats
formats={customFormats}
/>
</TableRowCell>
<RelativeDateCellConnector
date={date}
/>
@ -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,

@ -26,6 +26,12 @@ const columns = [
label: 'Quality',
isVisible: true
},
{
name: 'customFormats',
label: 'Custom Formats',
isSortable: false,
isVisible: true
},
{
name: 'date',
label: 'Date',

@ -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}
>
<MovieFormats
formats={quality.customFormats}
formats={customFormats}
/>
</TableRowCell>
@ -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,

@ -123,9 +123,6 @@ class CustomFormat extends Component {
<div>
Are you sure you want to delete custom format '{name}'?
</div>
<div>
This will remove all associations to this format in the DB. This may result in existing files being updated.
</div>
</div>
}
confirmLabel="Delete"

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

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

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

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

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

@ -11,11 +11,15 @@ namespace NzbDrone.Api.Qualities
public class CustomFormatModule : RadarrRestModule<CustomFormatResource>
{
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()
};
}
}

@ -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<FormatTagMatchResultResource> ToResource(this IList<FormatTagMatchResult> models)
public static List<FormatTagMatchResultResource> ToResource(this IList<CustomFormatMatchResult> models)
{
return models.Select(ToResource).ToList();
}

@ -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<IMainDatabase>(new MainDatabase(null));
container.Resolve<IAppFolderFactory>().Register();
// A dummy custom format repository since this isn't a DB test
var mockCustomFormat = Mocker.GetMock<ICustomFormatRepository>();
mockCustomFormat.Setup(x => x.All()).Returns(new List<CustomFormatDefinition>());
container.Register<ICustomFormatRepository>(mockCustomFormat.Object);
Mocker.SetConstant(container);
var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>()

@ -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<CustomFormat> { _customFormat1 };
var second = new List<CustomFormat> { _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<CustomFormat> { _customFormat2 };
var second = new List<CustomFormat> { _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<CustomFormat> { _customFormat3 };
var second = new List<CustomFormat> { _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<CustomFormat> { _customFormat3, _customFormat4 };
var second = new List<CustomFormat> { _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<CustomFormat> { _customFormat1, _customFormat3 };
var second = new List<CustomFormat> { _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<CustomFormat> { _customFormat1, _customFormat2 };
var second = new List<CustomFormat> { _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<CustomFormat> { _customFormat1, _customFormat2, _customFormat3 };
var second = new List<CustomFormat> { _customFormat4 };
var compare = Subject.Compare(first, second);
compare.Should().BeLessThan(0);
}
}
}

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

@ -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<remove_custom_formats_from_quality_model>
{
[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<string>("SELECT ParsedMovieInfo FROM PendingReleases").First();
json.Should().NotContain("customFormats");
var pending = db.Query<ParsedMovieInfo>("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<string>("SELECT ParsedMovieInfo FROM PendingReleases").First();
json.Should().NotContain("customFormats");
json.Should().NotContain("resolution");
var pending = db.Query<ParsedMovieInfo>("SELECT ParsedMovieInfo FROM PendingReleases").First();
pending.Quality.Quality.Should().Be(Quality.Bluray1080p);
pending.Languages.Should().BeEquivalentTo(new List<Language> { Language.English });
}
}
}

@ -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<CustomFormat> { _format1 };
_remoteMovie.CustomFormats = new List<CustomFormat> { _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<CustomFormat> { _format2 };
_remoteMovie.CustomFormats = new List<CustomFormat> { _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<CustomFormat> { _format2, _format1 };
_remoteMovie.CustomFormats = new List<CustomFormat> { _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<CustomFormat> { _format2, _format1 };
_remoteMovie.CustomFormats = new List<CustomFormat> { _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<CustomFormat> { };
_remoteMovie.CustomFormats = new List<CustomFormat> { };
_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<CustomFormat> { };
_remoteMovie.CustomFormats = new List<CustomFormat> { };
_remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(CustomFormat.None.Name, _format1.Name, _format2.Name);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();

@ -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<UpgradableSpecification>
public class CutoffSpecificationFixture : CoreTest<CutoffSpecification>
{
private CustomFormat _customFormat;
private RemoteMovie _remoteMovie;
[SetUp]
public void Setup()
{
Mocker.SetConstant<IUpgradableSpecification>(Mocker.Resolve<UpgradableSpecification>());
_remoteMovie = new RemoteMovie()
{
Movie = Builder<Movie>.CreateNew().Build(),
ParsedMovieInfo = Builder<ParsedMovieInfo>.CreateNew().With(x => x.Quality = null).Build()
};
GivenOldCustomFormats(new List<CustomFormat>());
}
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<MovieFile>.CreateNew().With(x => x.Quality = quality).Build();
}
private void GivenNewQuality(QualityModel quality)
{
_remoteMovie.ParsedMovieInfo.Quality = quality;
}
private void GivenOldCustomFormats(List<CustomFormat> formats)
{
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<MovieFile>()))
.Returns(formats);
}
private void GivenNewCustomFormats(List<CustomFormat> 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> { CustomFormat.None };
var newQ = new QualityModel(Quality.Bluray1080p);
newQ.CustomFormats = new List<CustomFormat> { _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> { CustomFormat.None });
GivenNewCustomFormats(new List<CustomFormat> { _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();
}
}
}

@ -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<UpgradableSpecification>();
_upgradeHistory = Mocker.Resolve<HistorySpecification>();
CustomFormatsFixture.GivenCustomFormats(CustomFormat.None);
_fakeMovie = Builder<Movie>.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<CustomFormat>()
};
_upgradableQuality = new QualityModel(Quality.SDTV, new Revision(version: 1));
@ -51,6 +63,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableCompletedDownloadHandling)
.Returns(true);
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<History.History>()))
.Returns(new List<CustomFormat>());
}
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<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<History.History>()))
.Returns(new List<CustomFormat>());
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));

@ -60,6 +60,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
remoteMovie.Release.DownloadProtocol = downloadProtocol;
remoteMovie.Release.Title = "A Movie 1998";
remoteMovie.CustomFormats = new List<CustomFormat>();
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<DownloadDecision>();
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<DownloadDecision>();
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<CustomFormat> { _customFormat1, _customFormat2 });
var remoteMovie2 = GivenRemoteMovie(quality2);
remoteMovie2.CustomFormats.AddRange(new List<CustomFormat> { _customFormat1, _customFormat2 });
var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteMovie1));

@ -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<UpgradableSpecification>
{
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<CustomFormat>(), Quality.SDTV, 2, new List<CustomFormat>(), true },
new object[] { Quality.SDTV, 1, new List<CustomFormat> { _customFormat1 }, Quality.SDTV, 2, new List<CustomFormat> { _customFormat1 }, true },
new object[] { Quality.SDTV, 1, new List<CustomFormat> { _customFormat1 }, Quality.SDTV, 2, new List<CustomFormat> { _customFormat2 }, true },
new object[] { Quality.SDTV, 1, new List<CustomFormat> { _customFormat2 }, Quality.SDTV, 2, new List<CustomFormat> { _customFormat1 }, true },
// Revision upgrade trumps custom format
new object[] { Quality.WEBDL720p, 1, new List<CustomFormat>(), Quality.WEBDL720p, 2, new List<CustomFormat>(), true },
new object[] { Quality.WEBDL720p, 1, new List<CustomFormat> { _customFormat1 }, Quality.WEBDL720p, 2, new List<CustomFormat> { _customFormat1 }, true },
new object[] { Quality.WEBDL720p, 1, new List<CustomFormat> { _customFormat1 }, Quality.WEBDL720p, 2, new List<CustomFormat> { _customFormat2 }, true },
new object[] { Quality.WEBDL720p, 1, new List<CustomFormat> { _customFormat2 }, Quality.WEBDL720p, 2, new List<CustomFormat> { _customFormat1 }, true },
// Custom formats apply if quality same
new object[] { Quality.SDTV, 1, new List<CustomFormat>(), Quality.SDTV, 1, new List<CustomFormat>(), false },
new object[] { Quality.SDTV, 1, new List<CustomFormat> { _customFormat1 }, Quality.SDTV, 1, new List<CustomFormat> { _customFormat1 }, false },
new object[] { Quality.SDTV, 1, new List<CustomFormat> { _customFormat1 }, Quality.SDTV, 1, new List<CustomFormat> { _customFormat2 }, true },
new object[] { Quality.SDTV, 1, new List<CustomFormat> { _customFormat2 }, Quality.SDTV, 1, new List<CustomFormat> { _customFormat1 }, false },
new object[] { Quality.WEBDL720p, 1, new List<CustomFormat>(), Quality.HDTV720p, 2, new List<CustomFormat>(), false },
new object[] { Quality.WEBDL720p, 1, new List<CustomFormat>(), Quality.HDTV720p, 2, new List<CustomFormat>(), false },
new object[] { Quality.WEBDL720p, 1, new List<CustomFormat>(), Quality.WEBDL720p, 1, new List<CustomFormat>(), false },
new object[] { Quality.WEBDL1080p, 1, new List<CustomFormat>(), Quality.WEBDL1080p, 1, new List<CustomFormat>(), 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<CustomFormat> currentFormats,
Quality newQuality,
int newVersion,
List<CustomFormat> 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<CustomFormat>(),
new QualityModel(Quality.DVD, new Revision(version: 1)),
new List<CustomFormat>())
.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<CustomFormat>(),
new QualityModel(Quality.HDTV720p, new Revision(version: 1)),
new List<CustomFormat>())
.Should().BeFalse();
}
}

@ -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<UpgradableSpecification>();
CustomFormatsFixture.GivenCustomFormats(CustomFormat.None);
_movie = Builder<Movie>.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<RemoteMovie>.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> { CustomFormat.None })
.Build();
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<ParsedMovieInfo>()))
.Returns(new List<CustomFormat>());
}
private void GivenEmptyQueue()
@ -87,12 +99,13 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_movie.Profile.Cutoff = Quality.Bluray1080p.Id;
var remoteMovie = Builder<RemoteMovie>.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> { CustomFormat.None })
.Build();
GivenQueue(new List<RemoteMovie> { remoteMovie });
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();

@ -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<IUpgradableSpecification>()
.Setup(s => s.IsUpgradable(It.IsAny<Profile>(), It.IsAny<QualityModel>(), It.IsAny<QualityModel>()))
.Setup(s => s.IsUpgradable(It.IsAny<Profile>(), It.IsAny<QualityModel>(), It.IsAny<List<CustomFormat>>(), It.IsAny<QualityModel>(), It.IsAny<List<CustomFormat>>()))
.Returns(true);
}

@ -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<UpgradableSpecification>();
_upgradeDisk = Mocker.Resolve<UpgradeDiskSpecification>();
CustomFormatsFixture.GivenCustomFormats(CustomFormat.None);
_firstFile = new MovieFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now };
var fakeSeries = Builder<Movie>.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<CustomFormat>()
};
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<MovieFile>()))
.Returns(new List<CustomFormat>());
}
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<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<MovieFile>()))
.Returns(new List<CustomFormat>());
_firstFile.Quality = new QualityModel(Quality.WEBDL1080p);
_parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p);
_upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();

@ -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<TMigration> : 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<TMigration> beforeMigration = null)
{
var db = WithTestDb(new MigrationContext(MigrationType, MigrationVersion)
return WithMigrationAction(beforeMigration).GetDirectDataMapper();
}
protected virtual IDbConnection WithDapperMigrationTestDb(Action<TMigration> beforeMigration = null)
{
return WithMigrationAction(beforeMigration).OpenConnection();
}
protected override void SetupLogging()
{
Mocker.SetConstant<ILoggerProvider>(Mocker.Resolve<MigrationLoggerProvider>());
}
private ITestDatabase WithMigrationAction(Action<TMigration> 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<ILoggerProvider>(Mocker.Resolve<MigrationLoggerProvider>());
}
[SetUp]
public override void SetupDb()
{
SetupContainer();
}
}
}

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

@ -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<MovieFile>(), It.IsAny<LocalMovie>(), It.IsAny<bool>()))
.Returns(new MovieFileMoveResult());
Mocker.GetMock<IHistoryService>()
.Setup(x => x.FindByDownloadId(It.IsAny<string>()))
.Returns(new List<History.History>());
_downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
}

@ -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<LocalMovie>()))
.Returns(new AugmentQualityResult(Source.TV, Confidence.Fallback, (int)Resolution.R720p, Confidence.Fallback, Modifier.NONE, Confidence.Fallback, new Revision(), new List<CustomFormat>()));
.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<LocalMovie>()))
.Returns(new AugmentQualityResult(Source.TV, Confidence.Default, (int)Resolution.R480p, Confidence.Default, Modifier.NONE, Confidence.Default, new Revision(), new List<CustomFormat>()));
.Returns(new AugmentQualityResult(Source.TV, Confidence.Default, (int)Resolution.R480p, Confidence.Default, Modifier.NONE, Confidence.Default, new Revision()));
}
private void GivenAugmenters(params Mock<IAugmentQuality>[] mocks)

@ -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<UpdateMovieFileQualityService>
{
private MovieFile _movieFile;
private QualityModel _oldQuality;
private QualityModel _newQuality;
private ParsedMovieInfo _newInfo;
[SetUp]
public void Setup()
{
_movieFile = Builder<MovieFile>.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<CustomFormat> { format };
_newInfo = new ParsedMovieInfo
{
Quality = _newQuality
};
Mocker.GetMock<IMediaFileService>().Setup(s => s.GetMovies(It.IsAny<IEnumerable<int>>()))
.Returns(new List<MovieFile> { _movieFile });
Mocker.GetMock<IHistoryService>().Setup(s => s.GetByMovieId(It.IsAny<int>(), null))
.Returns(new List<History.History>());
}
private void ExecuteCommand()
{
Subject.Execute(new UpdateMovieFileQualityCommand(new List<int> { 0 }));
}
[Test]
public void should_not_update_if_unable_to_parse()
{
ExecuteCommand();
ExceptionVerification.ExpectedWarns(1);
Mocker.GetMock<IMediaFileService>().Verify(s => s.Update(It.IsAny<MovieFile>()), Times.Never());
}
[Test]
public void should_update_with_new_formats()
{
Mocker.GetMock<IParsingService>().Setup(s => s.ParseMovieInfo(It.IsAny<string>(), It.IsAny<List<object>>()))
.Returns(_newInfo);
ExecuteCommand();
Mocker.GetMock<IMediaFileService>().Verify(s => s.Update(It.Is<MovieFile>(f => f.Quality.CustomFormats == _newQuality.CustomFormats)), Times.Once());
}
[Test]
public void should_use_imported_history_title()
{
var imported = Builder<History.History>.CreateNew()
.With(h => h.EventType = HistoryEventType.DownloadFolderImported)
.With(h => h.SourceTitle = "My Movie 2018.mkv").Build();
Mocker.GetMock<IHistoryService>().Setup(s => s.GetByMovieId(It.IsAny<int>(), null))
.Returns(new List<History.History> { imported });
Mocker.GetMock<IParsingService>().Setup(s => s.ParseMovieInfo("My Movie 2018.mkv", It.IsAny<List<object>>()))
.Returns(_newInfo);
ExecuteCommand();
Mocker.GetMock<IParsingService>().Verify(s => s.ParseMovieInfo("My Movie 2018.mkv", It.IsAny<List<object>>()));
}
}
}

@ -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<ProfileRepository>();
Mocker.SetConstant<IProfileRepository>(_profileRepository);
Mocker.GetMock<ICustomFormatService>()
.Setup(x => x.All())
.Returns(new List<CustomFormat>());
}
[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);

@ -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<CustomFormat> { format1 };
MovieInfo.Quality.CustomFormats = new List<CustomFormat> { 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<CustomFormat> { 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()
{

@ -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<CustomFormat> { _customFormat1 } };
var second = new QualityModel(Quality.DVD) { CustomFormats = new List<CustomFormat> { _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<CustomFormat> { _customFormat2 } };
var second = new QualityModel(Quality.DVD) { CustomFormats = new List<CustomFormat> { _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<CustomFormat> { _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<CustomFormat> { _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<CustomFormat> { _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<CustomFormat> { _customFormat1, _customFormat2 };
var second = _customFormat2.Id;
var compare = Subject.Compare(first, second);
compare.Should().Be(0);
}
}
}

@ -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<Language> Languages { get; set; }

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

@ -7,10 +7,6 @@ namespace NzbDrone.Core.CustomFormats
{
public class CustomFormat : ModelBase, IEquatable<CustomFormat>
{
public string Name { get; set; }
public List<FormatTag> 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<FormatTag>()
};
public string Name { get; set; }
public List<FormatTag> 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<CustomFormat> formats)
{
return string.Join(", ", formats.Select(f => f.ToString()));
}
public static List<CustomFormat> WithNone(this IEnumerable<CustomFormat> formats)
{
var list = formats.ToList();
if (list.Any())
{
return list;
}
return new List<CustomFormat> { CustomFormat.None };
return Id;
}
}
}

@ -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<CustomFormat> ParseCustomFormat(ParsedMovieInfo movieInfo);
List<CustomFormat> ParseCustomFormat(MovieFile movieFile);
List<CustomFormat> ParseCustomFormat(Blacklist blacklist);
List<CustomFormat> ParseCustomFormat(History.History history);
List<CustomFormatMatchResult> 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<CustomFormat> ParseCustomFormat(ParsedMovieInfo movieInfo)
{
return MatchFormatTags(movieInfo)
.Where(m => m.GoodMatch)
.Select(r => r.CustomFormat)
.ToList();
}
public List<CustomFormat> ParseCustomFormat(MovieFile movieFile)
{
return MatchFormatTags(movieFile)
.Where(m => m.GoodMatch)
.Select(r => r.CustomFormat)
.ToList();
}
public List<CustomFormat> ParseCustomFormat(Blacklist blacklist)
{
return MatchFormatTags(blacklist)
.Where(m => m.GoodMatch)
.Select(r => r.CustomFormat)
.ToList();
}
public List<CustomFormat> ParseCustomFormat(History.History history)
{
return MatchFormatTags(history)
.Where(m => m.GoodMatch)
.Select(r => r.CustomFormat)
.ToList();
}
public List<CustomFormatMatchResult> MatchFormatTags(ParsedMovieInfo movieInfo)
{
var formats = _formatService.All();
var matches = new List<CustomFormatMatchResult>();
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<CustomFormatMatchResult> 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<string, object>
{
{ "IndexerFlags", file.IndexerFlags },
{ "Size", file.Size },
{ "Filename", System.IO.Path.GetFileName(file.RelativePath) }
}
};
return MatchFormatTags(info);
}
private List<CustomFormatMatchResult> 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<string, object>
{
{ "IndexerFlags", blacklist.IndexerFlags },
{ "Size", blacklist.Size }
}
};
return MatchFormatTags(info);
}
private List<CustomFormatMatchResult> 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<string, object>
{
{ "IndexerFlags", flags },
{ "Size", size }
}
};
return MatchFormatTags(info);
}
}
}

@ -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<FormatTag> FormatTags { get; set; }
public static implicit operator CustomFormat(CustomFormatDefinition def) => new CustomFormat { Id = def.Id, Name = def.Name, FormatTags = def.FormatTags };
}
}

@ -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<FormatTagMatchesGroup> GroupMatches { get; set; }
public bool GoodMatch => GroupMatches.All(g => g.DidMatch);
}
}

@ -3,11 +3,11 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.CustomFormats
{
public interface ICustomFormatRepository : IBasicRepository<CustomFormatDefinition>
public interface ICustomFormatRepository : IBasicRepository<CustomFormat>
{
}
public class CustomFormatRepository : BasicRepository<CustomFormatDefinition>, ICustomFormatRepository
public class CustomFormatRepository : BasicRepository<CustomFormat>, ICustomFormatRepository
{
public CustomFormatRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)

@ -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<IProfileService>();
}
return _profileService;
}
}
private readonly IContainer _container;
private readonly IEventAggregator _eventAggregator;
private readonly ICached<Dictionary<int, CustomFormat>> _cache;
private readonly Logger _logger;
public static Dictionary<int, CustomFormat> 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<Dictionary<int, CustomFormat>>(typeof(CustomFormat), "formats");
_historyService = historyService;
_logger = logger;
// Fill up the cache for subsequent DB lookups
All();
}
public void Update(CustomFormat customFormat)
private Dictionary<int, CustomFormat> AllDictionary()
{
_formatRepository.Update(customFormat);
_cache.Clear();
return _cache.Get("all", () => _formatRepository.All().ToDictionary(m => m.Id));
}
public CustomFormat Insert(CustomFormat customFormat)
public List<CustomFormat> 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<IHistoryRepository>();
DeleteInRepo(historyRepo,
h => h.Quality.CustomFormats,
(h, f) =>
{
h.Quality.CustomFormats = f;
return h;
},
id);
//Then Blacklist:
var blacklistRepo = _container.Resolve<IBlacklistRepository>();
DeleteInRepo(blacklistRepo,
h => h.Quality.CustomFormats,
(h, f) =>
{
h.Quality.CustomFormats = f;
return h;
},
id);
//Then MovieFiles:
var moviefileRepo = _container.Resolve<IMediaFileRepository>();
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<TModel>(IBasicRepository<TModel> repository,
Func<TModel, List<CustomFormat>> queryFunc,
Func<TModel, List<CustomFormat>, 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<int, CustomFormat> 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<CustomFormat> 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<string, List<CustomFormat>> Templates
{
get
{
return new Dictionary<string, List<CustomFormat>>
public static Dictionary<string, List<CustomFormat>> Templates => new Dictionary<string, List<CustomFormat>>
{
{
"Easy", new List<CustomFormat>
@ -207,7 +102,5 @@ namespace NzbDrone.Core.CustomFormats
}
}
};
}
}
}
}

@ -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<List<CustomFormat>>
{
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<CustomFormat> left, List<CustomFormat> 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);
}
}
}

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

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

@ -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(@"^(?<type>R|S|M|E|L|C|I|G)(_((?<m_r>RX)|(?<m_re>RQ)|(?<m_n>N)){0,3})?_(?<value>.*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex SizeTagRegex = new Regex(@"(?<min>\d+(\.\d+)?)\s*<>\s*(?<max>\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
}
}

@ -1,44 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Core.CustomFormats
{
public class FormatTagMatchResult
{
public FormatTagMatchResult()
{
GroupMatches = new List<FormatTagMatchesGroup>();
}
public CustomFormat CustomFormat { get; set; }
public List<FormatTagMatchesGroup> GroupMatches { get; set; }
public bool GoodMatch { get; set; }
}
public class FormatTagMatchesGroup
{
public FormatTagMatchesGroup()
{
Matches = new Dictionary<FormatTag, bool>();
}
public FormatTagMatchesGroup(TagType type, Dictionary<FormatTag, bool> 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<FormatTag, bool> Matches { get; set; }
}
}

@ -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<FormatTag, bool> Matches { get; set; }
public bool DidMatch => !(Matches.Any(m => m.Key.TagModifier.HasFlag(TagModifier.AbsolutelyRequired) && m.Value == false) ||
Matches.All(m => m.Value == false));
}
}

@ -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<CustomFormat>
{
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<CustomFormat>
{
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)

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

@ -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<QualityModel165>());
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<ParsedMovieInfo164>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ParsedMovieInfo165>());
var rows = conn.Query<ParsedMovieInfoData164>("SELECT Id, ParsedMovieInfo from PendingReleases");
var newRows = new List<ParsedMovieInfoData165>();
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<QualityRow>($"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<BlacklistData>("SELECT Blacklist.Id, Blacklist.TorrentInfoHash, History.Data " +
"FROM Blacklist " +
"JOIN History ON Blacklist.MovieId = History.MovieId " +
"WHERE History.EventType = 1");
var toUpdate = new List<IndexerFlagsItem>();
foreach (var item in blacklists)
{
var dict = Json.Deserialize<Dictionary<string, string>>(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<MovieFileData>("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<IndexerFlagsItem>();
foreach (var item in movieFiles)
{
var dict = Json.Deserialize<Dictionary<string, string>>(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<string> 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<int> 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; }
}
}
}

@ -111,7 +111,7 @@ namespace NzbDrone.Core.Datastore
.Ignore(d => d.GroupName)
.Ignore(d => d.Weight);
Mapper.Entity<CustomFormatDefinition>("CustomFormats").RegisterModel();
Mapper.Entity<CustomFormat>("CustomFormats").RegisterModel();
Mapper.Entity<Profile>("Profiles").RegisterModel();
Mapper.Entity<Log>("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<List<ProfileQualityItem>>(new QualityIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfileFormatItem>>(new CustomFormatIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<FormatTag>>(new QualityTagStringConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<QualityModel>(new CustomFormatIntConverter(), new QualityIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<QualityModel>(new QualityIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<Dictionary<string, string>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<IDictionary<string, string>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>());
@ -160,7 +159,7 @@ namespace NzbDrone.Core.Datastore
SqlMapper.AddTypeHandler(new DapperLanguageIntConverter());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<Language>>(new LanguageIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<string>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ParsedMovieInfo>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ParsedMovieInfo>(new QualityIntConverter(), new LanguageIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ReleaseInfo>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<HashSet<int>>());
SqlMapper.AddTypeHandler(new OsPathConverter());

@ -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, TValue>(TSubject left, TSubject right, Func<TSubject, TValue> funcValue)
where TValue : IComparable<TValue>
{
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)

@ -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<IDecisionEngineSpecification> _specifications;
private readonly IParsingService _parsingService;
private readonly IConfigService _configService;
private readonly IQualityDefinitionService _definitionService;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly Logger _logger;
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> 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;

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

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

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

@ -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)
{

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

@ -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<CustomFormat> currentCustomFormats, QualityModel newQuality, List<CustomFormat> newCustomFormats);
bool CutoffNotMet(Profile profile, QualityModel currentQuality, List<CustomFormat> 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<CustomFormat> currentCustomFormats, QualityModel newQuality, List<CustomFormat> 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<CustomFormat> currentCustomFormats, QualityModel newQuality, List<CustomFormat> 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<CustomFormat> currentFormats)
{
var cutoff = new List<CustomFormat> { 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<CustomFormat> 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<CustomFormat> currentCustomFormats, QualityModel newQuality, List<CustomFormat> 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;

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

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

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

@ -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<TrackedDownload> _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<TrackedDownload>(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)
{

@ -1,17 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.MediaFiles.Commands
{
public class UpdateMovieFileQualityCommand : Command
{
public IEnumerable<int> MovieFileIds { get; set; }
public override bool SendUpdatesToClient => true;
public UpdateMovieFileQualityCommand(IEnumerable<int> movieFileIds)
{
MovieFileIds = movieFileIds;
}
}
}

@ -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<Language> Languages { get; set; }
public MediaInfoModel MediaInfo { get; set; }

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

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

@ -24,8 +24,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenter
confidence,
quality.Quality.Modifier,
confidence,
quality.Revision,
quality.CustomFormats);
quality.Revision);
}
}
}

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

@ -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<CustomFormat> CustomFormats { get; set; }
public AugmentQualityResult(Source source,
Confidence sourceConfidence,
int resolution,
Confidence resolutionConfidence,
Modifier modifier,
Confidence modifierConfidence,
Revision revision,
List<CustomFormat> 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);
}
}
}

@ -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)
{

@ -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<UpdateMovieFileQualityCommand>
{
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<object> { 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++;
}
}
}
}

@ -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<CustomFormat> existing)
{
existing.Add(format);
movieInfo.ExtraInfo["AdditionalFormats"] = existing;
}
else
{
movieInfo.ExtraInfo["AdditionalFormats"] = new List<CustomFormat> { format };
}
}
return movieInfo;
}
}
}

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

@ -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<string, object> ExtraInfo = new Dictionary<string, object>();
public List<Language> Languages = new List<Language>();
public List<Language> Languages { get; set; } = new List<Language>();
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<string, object> ExtraInfo { get; set; } = new Dictionary<string, object>();
public override string ToString()
{

@ -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<CustomFormat> CustomFormats { get; set; }
public Movie Movie { get; set; }
public MappingResultType MappingResult { get; set; }
public bool DownloadAllowed { get; set; }

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

@ -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<object> helpers = null);
ParsedMovieInfo ParseMinimalMovieInfo(string path, bool isDir = false);
ParsedMovieInfo ParseMinimalPathMovieInfo(string path);
List<FormatTagMatchResult> MatchFormatTags(ParsedMovieInfo movieInfo);
}
public class ParsingService : IParsingService
{
private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralMappings;
private readonly IMovieService _movieService;
private readonly IConfigService _config;
private readonly IQualityDefinitionService _qualityDefinitionService;
private readonly ICustomFormatService _formatService;
private readonly IEnumerable<IAugmentParsedMovieInfo> _augmenters;
private readonly Logger _logger;
private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralMappings;
public ParsingService(
IMovieService movieService,
public ParsingService(IMovieService movieService,
IConfigService configService,
IQualityDefinitionService qualityDefinitionService,
ICustomFormatService formatService,
IEnumerable<IAugmentParsedMovieInfo> 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<CustomFormat> ParseCustomFormat(ParsedMovieInfo movieInfo)
{
var matches = MatchFormatTags(movieInfo);
var goodMatches = matches.Where(m => m.GoodMatch);
return goodMatches.Select(r => r.CustomFormat).ToList();
}
public List<FormatTagMatchResult> MatchFormatTags(ParsedMovieInfo movieInfo)
{
var formats = _formatService.All();
if (movieInfo.ExtraInfo.GetValueOrDefault("AdditionalFormats") is List<CustomFormat> additionalFormats)
{
formats.AddRange(additionalFormats);
}
var matches = new List<FormatTagMatchResult>();
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);

@ -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<int> GetIndices(List<CustomFormat> formats)
{
var allFormats = formats.Any() ? formats : new List<CustomFormat> { CustomFormat.None };
return allFormats.Select(f => FormatItems.FindIndex(v => Equals(v.Format, f))).ToList();
}
}
}

@ -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<Profile>, 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<Profile> 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)

@ -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<Profile> 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<ApplicationStartedEvent>
public class ProfileService : IProfileService,
IHandle<ApplicationStartedEvent>,
IHandle<CustomFormatAddedEvent>,
IHandle<CustomFormatDeletedEvent>
{
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<ProfileQualityItem>();
var formatItems = new List<ProfileFormatItem>();
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<ProfileFormatItem>
{
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<ProfileFormatItem>
{
new ProfileFormatItem
{
Id = 0,
Allowed = true,
Format = CustomFormat.None
}
}
FormatItems = formatItems
};
qualityProfile.FormatItems.AddRange(formatItems);
return qualityProfile;
}

@ -0,0 +1,12 @@
namespace NzbDrone.Core.Qualities
{
public enum Modifier
{
NONE = 0,
REGIONAL,
SCREENER,
RAWHD,
BRDISK,
REMUX
}
}

@ -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<CustomFormat> 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<CustomFormat> customFormats = null)
public QualityModel(Quality quality, Revision revision = null)
{
Quality = quality;
Revision = revision ?? new Revision();
CustomFormats = customFormats ?? new List<CustomFormat>();
}
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()

@ -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<Quality>, IComparer<QualityModel>, IComparer<CustomFormat>, IComparer<List<CustomFormat>>
public class QualityModelComparer : IComparer<Quality>, IComparer<QualityModel>
{
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<CustomFormat> left, List<CustomFormat> right)
{
List<int> leftIndicies = GetIndicies(left, _profile);
List<int> rightIndicies = GetIndicies(right, _profile);
int leftTotal = leftIndicies.Sum();
int rightTotal = rightIndicies.Sum();
return leftTotal.CompareTo(rightTotal);
}
public static List<int> GetIndicies(List<CustomFormat> 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<CustomFormat> 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();
}
}
}

@ -0,0 +1,16 @@
namespace NzbDrone.Core.Qualities
{
public enum Source
{
UNKNOWN = 0,
CAM,
TELESYNC,
TELECINE,
WORKPRINT,
DVD,
TV,
WEBDL,
WEBRIP,
BLURAY
}
}

@ -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<IBroadcastSignalRMessage>();
_container.Register<IBroadcastSignalRMessage>(mockBroadcaster.Object);
// A dummy custom format repository since this isn't a DB test
var mockCustomFormat = Mocker.GetMock<ICustomFormatRepository>();
mockCustomFormat.Setup(x => x.All()).Returns(new List<CustomFormatDefinition>());
_container.Register<ICustomFormatRepository>(mockCustomFormat.Object);
}
[Test]

@ -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<BlacklistResource>
{
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<BlacklistResource, NzbDrone.Core.Blacklisting.Blacklist>("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)

@ -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<Language> Languages { get; set; }
public QualityModel Quality { get; set; }
public List<CustomFormatResource> 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,

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

@ -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<CustomFormatResource> ToResource(this IEnumerable<CustomFormat> 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<CustomFormatResource> ToResource(this IEnumerable<CustomFormat> models)
{
return models.Select(m => m.ToResource()).ToList();
}
}
}

@ -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<FormatTagGroupMatchesResource> GroupMatches { get; set; }
@ -21,27 +21,27 @@ namespace Radarr.Api.V3.Qualities
public class CustomFormatTestResource : RestResource
{
public List<FormatTagMatchResultResource> Matches { get; set; }
public List<CustomFormatMatchResultResource> Matches { get; set; }
public List<CustomFormatResource> 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<FormatTagMatchResultResource> ToResource(this IList<FormatTagMatchResult> models)
public static List<CustomFormatMatchResultResource> ToResource(this IList<CustomFormatMatchResult> models)
{
return models.Select(ToResource).ToList();
}

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

@ -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<HistoryResource>
{
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;

@ -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<Language> Languages { get; set; }
public QualityModel Quality { get; set; }
public List<CustomFormatResource> 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,

@ -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<CustomFormatResource> 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,

@ -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<MovieFileResource> 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<MovieFileResource>();
}
var resource = file.ToResource(movie, _qualityUpgradableSpecification);
file.Movie = movie;
resource.CustomFormats = _formatCalculator.ParseCustomFormat(file).ToResource();
return new List<MovieFileResource> { 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);

@ -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<CustomFormatResource> CustomFormats { get; set; }
public MediaInfoResource MediaInfo { get; set; }
public bool QualityCutoffNotMet { get; set; }
public List<Language> 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)
};
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save