New: Calculate custom formats on demand

pull/4099/head
ta264 5 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()
{
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
GivenProfile(new Profile
{
Cutoff = Quality.HDTV720p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatCutoff = CustomFormat.None.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems("None", "My Format")
},
old,
newQ).Should().BeFalse();
});
GivenFileQuality(new QualityModel(Quality.HDTV720p));
GivenNewQuality(new QualityModel(Quality.Bluray1080p));
GivenCustomFormatHigher();
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() })
.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();
@ -41,7 +48,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_remoteMovie = Builder<RemoteMovie>.CreateNew()
.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()
@ -92,6 +104,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
Quality = new QualityModel(Quality.SDTV)
})
.With(x => x.CustomFormats = new List<CustomFormat> { CustomFormat.None })
.Build();
GivenQueue(new List<RemoteMovie> { remoteMovie });

@ -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,10 +31,17 @@ 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(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();
@ -38,7 +49,12 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
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
SetupContainer();
}
protected virtual IDirectDataMapper WithMigrationTestDb(Action<TMigration> beforeMigration = null)
{
var attrib = (MigrationAttribute)Attribute.GetCustomAttribute(typeof(TMigration), typeof(MigrationAttribute));
return attrib.Version;
return WithMigrationAction(beforeMigration).GetDirectDataMapper();
}
protected virtual IDbConnection WithDapperMigrationTestDb(Action<TMigration> beforeMigration = null)
{
return WithMigrationAction(beforeMigration).OpenConnection();
}
protected virtual IDirectDataMapper WithMigrationTestDb(Action<TMigration> beforeMigration = null)
protected override void SetupLogging()
{
var db = WithTestDb(new MigrationContext(MigrationType, MigrationVersion)
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,6 +23,10 @@ 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]

@ -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)
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)
{
var ret = _formatRepository.Insert(customFormat);
try
{
ProfileService.AddCustomFormat(ret);
}
catch (Exception e)
public List<CustomFormat> All()
{
_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)
{
_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)
public CustomFormat GetById(int id)
{
_logger.Error(e, "Failed to delete format with id {} from other repositories! Format will not be deleted!", id);
throw;
return AllDictionary()[id];
}
//Finally delete the format for real!
_formatRepository.Delete(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()
{
return _cache.Get("all", () =>
public void Delete(int id)
{
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>
@ -208,6 +103,4 @@ 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 DoesItMatchWithoutMods(ParsedMovieInfo movieInfo)
{
switch (TagType)
private bool MatchString(string compared)
{
case TagType.Edition:
case TagType.Custom:
string compared = null;
if (TagType == TagType.Custom)
if (compared == null)
{
compared = movieInfo.SimpleReleaseTitle;
}
else
{
compared = movieInfo.Edition;
return false;
}
if (TagModifier.HasFlag(TagModifier.Regex))
{
Regex regexValue = (Regex)Value;
var regexValue = (Regex)Value;
return regexValue.IsMatch(compared);
}
else
{
string stringValue = (string)Value;
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:
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,10 +123,10 @@ namespace NzbDrone.Core.CustomFormats
{
TagModifier |= TagModifier.Not;
}
}
switch (type)
private void ParseResolutionType(string value)
{
case "r":
TagType = TagType.Resolution;
switch (value)
{
@ -139,10 +145,13 @@ namespace NzbDrone.Core.CustomFormats
case "480":
Value = Resolution.R480p;
break;
default:
break;
}
}
break;
case "s":
private void ParseSourceType(string value)
{
TagType = TagType.Source;
switch (value)
{
@ -170,10 +179,13 @@ namespace NzbDrone.Core.CustomFormats
case "bluray":
Value = Source.BLURAY;
break;
default:
break;
}
}
break;
case "m":
private void ParseModifierType(string value)
{
TagType = TagType.Modifier;
switch (value)
{
@ -192,27 +204,13 @@ namespace NzbDrone.Core.CustomFormats
case "remux":
Value = Modifier.REMUX;
break;
}
default:
break;
case "e":
TagType = TagType.Edition;
if (TagModifier.HasFlag(TagModifier.Regex))
{
Value = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
else
{
Value = value;
}
break;
case "l":
TagType = TagType.Language;
Value = Parser.LanguageParser.ParseLanguages(value).First();
break;
case "i":
#if !LIBRARY
private void ParseIndexerFlagType(string value)
{
TagType = TagType.Indexer;
var flagValues = Enum.GetValues(typeof(IndexerFlags));
@ -227,18 +225,19 @@ namespace NzbDrone.Core.CustomFormats
Value = flagValue;
break;
}
#endif
break;
case "g":
}
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());
break;
case "c":
default:
TagType = TagType.Custom;
}
private void ParseString(string value)
{
if (TagModifier.HasFlag(TagModifier.Regex))
{
Value = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
@ -247,7 +246,46 @@ namespace NzbDrone.Core.CustomFormats
{
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":
ParseResolutionType(value);
break;
case "s":
ParseSourceType(value);
break;
case "m":
ParseModifierType(value);
break;
case "e":
TagType = TagType.Edition;
ParseString(value);
break;
case "l":
TagType = TagType.Language;
Value = LanguageParser.ParseLanguages(value).First();
break;
case "i":
#if !LIBRARY
ParseIndexerFlagType(value);
#endif
break;
case "g":
ParseSizeType(value);
break;
case "c":
default:
TagType = TagType.Custom;
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,25 +55,11 @@ 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.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 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);
}
private int ComparePreferredWords(DownloadDecision x, DownloadDecision y)
{
return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie =>

@ -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,
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,
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,
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,
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)
{
int compare = new QualityModelComparer(profile).Compare(newQuality, currentQuality);
if (compare <= 0)
_logger.Debug("New item has a better quality");
return true;
}
if (qualityCompare < 0)
{
_logger.Debug("Existing item has better quality, skipping");
return false;
}
if (IsRevisionUpgrade(currentQuality, newQuality))
// 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)
{
_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,6 +44,7 @@ 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;
@ -53,6 +55,7 @@ namespace NzbDrone.Core.Download.Pending
IDelayProfileService delayProfileService,
ITaskManager taskManager,
IConfigService configService,
ICustomFormatCalculationService formatCalculator,
IEventAggregator eventAggregator,
Logger logger)
{
@ -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,6 +28,7 @@ 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;
@ -34,6 +36,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
ICacheManager cacheManager,
IHistoryService historyService,
IConfigService config,
ICustomFormatCalculationService formatCalculator,
IEventAggregator eventAggregator,
Logger logger)
{
@ -41,6 +44,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
_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,6 +27,7 @@ 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;
@ -33,6 +35,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
IMediaFileService mediaFileService,
IExtraService extraService,
IDiskProvider diskProvider,
IHistoryService historyService,
IEventAggregator eventAggregator,
Logger logger)
{
@ -40,6 +43,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
_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,
ICustomFormatService formatService,
IMovieService movieService,
INetImportFactory netImportFactory,
ICustomFormatService formatService,
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;
@ -48,51 +46,12 @@ namespace NzbDrone.Core.Qualities
{
int result = Compare(left.Quality, right.Quality, respectGroupOrder);
if (result == 0)
{
result = Compare(left.CustomFormats, right.CustomFormats);
if (result == 0)
{
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