1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-10-05 15:47:20 +02:00

New: Calculate custom formats on demand

This commit is contained in:
ta264 2020-01-22 21:47:33 +00:00
parent 13701498ce
commit df101258c5
103 changed files with 1901 additions and 1346 deletions

View File

@ -331,10 +331,10 @@ stages:
artifactName: '$(testName)Tests' artifactName: '$(testName)Tests'
targetPath: $(testsFolder) targetPath: $(testsFolder)
- bash: | - bash: |
wget https://mediaarea.net/repo/deb/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-9_all.deb sudo dpkg -i repo-mediaarea_1.0-11_all.deb
sudo apt-get update 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 displayName: Install mediainfo
condition: and(succeeded(), eq(variables['osName'], 'Linux')) condition: and(succeeded(), eq(variables['osName'], 'Linux'))
- powershell: Set-Service SCardSvr -StartupType Manual - powershell: Set-Service SCardSvr -StartupType Manual

View File

@ -44,6 +44,7 @@ class BlacklistRow extends Component {
movie, movie,
sourceTitle, sourceTitle,
quality, quality,
customFormats,
languages, languages,
date, date,
protocol, protocol,
@ -112,11 +113,11 @@ class BlacklistRow extends Component {
); );
} }
if (name === 'quality.customFormats') { if (name === 'customFormats') {
return ( return (
<TableRowCell key={name}> <TableRowCell key={name}>
<MovieFormats <MovieFormats
formats={quality.customFormats} formats={customFormats}
/> />
</TableRowCell> </TableRowCell>
); );
@ -186,6 +187,7 @@ BlacklistRow.propTypes = {
movie: PropTypes.object.isRequired, movie: PropTypes.object.isRequired,
sourceTitle: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired,
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired,
date: PropTypes.string.isRequired, date: PropTypes.string.isRequired,
protocol: PropTypes.string.isRequired, protocol: PropTypes.string.isRequired,

View File

@ -54,6 +54,7 @@ class HistoryRow extends Component {
const { const {
movie, movie,
quality, quality,
customFormats,
languages, languages,
qualityCutoffNotMet, qualityCutoffNotMet,
eventType, eventType,
@ -126,11 +127,11 @@ class HistoryRow extends Component {
); );
} }
if (name === 'quality.customFormats') { if (name === 'customFormats') {
return ( return (
<TableRowCell key={name}> <TableRowCell key={name}>
<MovieFormats <MovieFormats
formats={quality.customFormats} formats={customFormats}
/> />
</TableRowCell> </TableRowCell>
); );
@ -219,6 +220,7 @@ HistoryRow.propTypes = {
languages: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired,
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
eventType: PropTypes.string.isRequired, eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired,
date: PropTypes.string.isRequired, date: PropTypes.string.isRequired,

View File

@ -72,6 +72,7 @@ class QueueRow extends Component {
errorMessage, errorMessage,
movie, movie,
quality, quality,
customFormats,
languages, languages,
protocol, protocol,
indexer, indexer,
@ -169,11 +170,11 @@ class QueueRow extends Component {
); );
} }
if (name === 'quality.customFormats') { if (name === 'customFormats') {
return ( return (
<TableRowCell key={name}> <TableRowCell key={name}>
<MovieFormats <MovieFormats
formats={quality.customFormats} formats={customFormats}
/> />
</TableRowCell> </TableRowCell>
); );
@ -329,6 +330,7 @@ QueueRow.propTypes = {
errorMessage: PropTypes.string, errorMessage: PropTypes.string,
movie: PropTypes.object, movie: PropTypes.object,
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object),
languages: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired,
protocol: PropTypes.string.isRequired, protocol: PropTypes.string.isRequired,
indexer: PropTypes.string, indexer: PropTypes.string,

View File

@ -113,6 +113,7 @@ class InteractiveSearchRow extends Component {
seeders, seeders,
leechers, leechers,
quality, quality,
customFormats,
languages, languages,
indexerFlags, indexerFlags,
rejections, rejections,
@ -177,7 +178,7 @@ class InteractiveSearchRow extends Component {
<TableRowCell className={styles.customFormat}> <TableRowCell className={styles.customFormat}>
<MovieFormats <MovieFormats
formats={quality.customFormats} formats={customFormats}
/> />
</TableRowCell> </TableRowCell>
@ -279,6 +280,7 @@ InteractiveSearchRow.propTypes = {
seeders: PropTypes.number, seeders: PropTypes.number,
leechers: PropTypes.number, leechers: PropTypes.number,
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired,
rejections: PropTypes.arrayOf(PropTypes.string).isRequired, rejections: PropTypes.arrayOf(PropTypes.string).isRequired,
indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired, indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,

View File

@ -9,6 +9,7 @@ import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import MovieQuality from 'Movie/MovieQuality'; import MovieQuality from 'Movie/MovieQuality';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage'; import MovieLanguage from 'Movie/MovieLanguage';
import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector'; import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector';
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell'; import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
@ -64,6 +65,7 @@ class MovieHistoryRow extends Component {
eventType, eventType,
sourceTitle, sourceTitle,
quality, quality,
customFormats,
languages, languages,
qualityCutoffNotMet, qualityCutoffNotMet,
date, date,
@ -98,6 +100,12 @@ class MovieHistoryRow extends Component {
/> />
</TableRowCell> </TableRowCell>
<TableRowCell key={name}>
<MovieFormats
formats={customFormats}
/>
</TableRowCell>
<RelativeDateCellConnector <RelativeDateCellConnector
date={date} date={date}
/> />
@ -152,6 +160,7 @@ MovieHistoryRow.propTypes = {
sourceTitle: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired,
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired,
date: PropTypes.string.isRequired, date: PropTypes.string.isRequired,
data: PropTypes.object.isRequired, data: PropTypes.object.isRequired,

View File

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

View File

@ -86,6 +86,7 @@ class MovieFileEditorRow extends Component {
size, size,
quality, quality,
qualityCutoffNotMet, qualityCutoffNotMet,
customFormats,
languages languages
} = this.props; } = this.props;
@ -173,7 +174,7 @@ class MovieFileEditorRow extends Component {
className={styles.formats} className={styles.formats}
> >
<MovieFormats <MovieFormats
formats={quality.customFormats} formats={customFormats}
/> />
</TableRowCell> </TableRowCell>
@ -233,6 +234,7 @@ MovieFileEditorRow.propTypes = {
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
relativePath: PropTypes.string.isRequired, relativePath: PropTypes.string.isRequired,
quality: PropTypes.object.isRequired, quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired, languages: PropTypes.arrayOf(PropTypes.object).isRequired,
mediaInfo: PropTypes.object.isRequired, mediaInfo: PropTypes.object.isRequired,

View File

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

View File

@ -16,6 +16,13 @@ const blacklistedProperties = [
'id' 'id'
]; ];
function createItemMap(data) {
return data.reduce((acc, d, index, array) => {
acc[d.id] = index;
return acc;
}, {});
}
export default function createHandleActions(handlers, defaultState, section) { export default function createHandleActions(handlers, defaultState, section) {
return handleActions({ return handleActions({
@ -42,7 +49,7 @@ export default function createHandleActions(handlers, defaultState, section) {
if (_.isArray(payload.data)) { if (_.isArray(payload.data)) {
newState.items = payload.data; newState.items = payload.data;
newState.itemMap = _.zipObject(_.map(payload.data, 'id'), _.range(payload.data.length)); newState.itemMap = createItemMap(newState.items);
} else { } else {
newState.item = payload.data; newState.item = payload.data;
} }
@ -67,7 +74,7 @@ export default function createHandleActions(handlers, defaultState, section) {
const items = newState.items; const items = newState.items;
if (!newState.itemMap) { 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; 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]; newState.items = [...newState.items];
_.remove(newState.items, { id: payload.id }); _.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); return updateSectionState(state, payloadSection, newState);
} }

View File

@ -51,9 +51,9 @@ export const defaultState = {
isVisible: true isVisible: true
}, },
{ {
name: 'quality.customFormats', name: 'customFormats',
label: 'Custom Formats', label: 'Formats',
isSortable: true, isSortable: false,
isVisible: true isVisible: true
}, },
{ {

View File

@ -52,9 +52,9 @@ export const defaultState = {
isVisible: true isVisible: true
}, },
{ {
name: 'quality.customFormats', name: 'customFormats',
label: 'Custom Formats', label: 'Formats',
isSortable: true, isSortable: false,
isVisible: true isVisible: true
}, },
{ {

View File

@ -81,9 +81,9 @@ export const defaultState = {
isVisible: true isVisible: true
}, },
{ {
name: 'quality.customFormats', name: 'customFormats',
label: 'Custom Formats', label: 'Formats',
isSortable: true, isSortable: false,
isVisible: true isVisible: true
}, },
{ {

View File

@ -35,7 +35,7 @@ protected HistoryResource MapToResource(Core.History.History model)
if (model.Movie != null) if (model.Movie != null)
{ {
resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(model.Movie.Profile, model.Quality); resource.QualityCutoffNotMet = _qualityUpgradableSpecification.QualityCutoffNotMet(model.Movie.Profile, model.Quality);
} }
return resource; return resource;

View File

@ -11,11 +11,15 @@ namespace NzbDrone.Api.Qualities
public class CustomFormatModule : RadarrRestModule<CustomFormatResource> public class CustomFormatModule : RadarrRestModule<CustomFormatResource>
{ {
private readonly ICustomFormatService _formatService; private readonly ICustomFormatService _formatService;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly IParsingService _parsingService; private readonly IParsingService _parsingService;
public CustomFormatModule(ICustomFormatService formatService, IParsingService parsingService) public CustomFormatModule(ICustomFormatService formatService,
ICustomFormatCalculationService formatCalculator,
IParsingService parsingService)
{ {
_formatService = formatService; _formatService = formatService;
_formatCalculator = formatCalculator;
_parsingService = parsingService; _parsingService = parsingService;
SharedValidator.RuleFor(c => c.Name).NotEmpty(); SharedValidator.RuleFor(c => c.Name).NotEmpty();
@ -103,8 +107,8 @@ private CustomFormatTestResource Test()
return new CustomFormatTestResource return new CustomFormatTestResource
{ {
Matches = _parsingService.MatchFormatTags(parsed).ToResource(), Matches = _formatCalculator.MatchFormatTags(parsed).ToResource(),
MatchedFormats = parsed.Quality.CustomFormats.ToResource() MatchedFormats = _formatCalculator.ParseCustomFormat(parsed).ToResource()
}; };
} }
@ -125,8 +129,8 @@ private CustomFormatTestResource TestWithNewModel()
return new CustomFormatTestResource return new CustomFormatTestResource
{ {
Matches = _parsingService.MatchFormatTags(parsed).ToResource(), Matches = _formatCalculator.MatchFormatTags(parsed).ToResource(),
MatchedFormats = parsed.Quality.CustomFormats.ToResource() MatchedFormats = _formatCalculator.ParseCustomFormat(parsed).ToResource()
}; };
} }
} }

View File

@ -27,7 +27,7 @@ public class CustomFormatTestResource : RestResource
public static class QualityTagMatchResultResourceMapper public static class QualityTagMatchResultResourceMapper
{ {
public static FormatTagMatchResultResource ToResource(this FormatTagMatchResult model) public static FormatTagMatchResultResource ToResource(this CustomFormatMatchResult model)
{ {
if (model == null) if (model == null)
{ {
@ -41,7 +41,7 @@ public static FormatTagMatchResultResource ToResource(this FormatTagMatchResult
}; };
} }
public static List<FormatTagMatchResultResource> ToResource(this IList<FormatTagMatchResult> models) public static List<FormatTagMatchResultResource> ToResource(this IList<CustomFormatMatchResult> models)
{ {
return models.Select(ToResource).ToList(); return models.Select(ToResource).ToList();
} }

View File

@ -1,9 +1,7 @@
using System.Collections.Generic; using System.Linq;
using System.Linq;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@ -22,11 +20,6 @@ public void event_handlers_should_be_unique()
container.Register<IMainDatabase>(new MainDatabase(null)); container.Register<IMainDatabase>(new MainDatabase(null));
container.Resolve<IAppFolderFactory>().Register(); 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); Mocker.SetConstant(container);
var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>() var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>()

View File

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

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.CustomFormats; using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.CustomFormats namespace NzbDrone.Core.Test.CustomFormats

View File

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

View File

@ -47,7 +47,7 @@ public void Setup()
[Test] [Test]
public void should_allow_if_format_is_defined_in_profile() 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); _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();
@ -56,7 +56,7 @@ public void should_allow_if_format_is_defined_in_profile()
[Test] [Test]
public void should_deny_if_format_is_defined_in_profile() 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); _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
@ -65,7 +65,7 @@ public void should_deny_if_format_is_defined_in_profile()
[Test] [Test]
public void should_deny_if_one_format_is_defined_in_profile() 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); _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
@ -74,7 +74,7 @@ public void should_deny_if_one_format_is_defined_in_profile()
[Test] [Test]
public void should_allow_if_all_format_is_defined_in_profile() 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); _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();
@ -83,7 +83,7 @@ public void should_allow_if_all_format_is_defined_in_profile()
[Test] [Test]
public void should_deny_if_no_format_was_parsed_and_none_not_in_profile() 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); _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
@ -92,7 +92,7 @@ public void should_deny_if_no_format_was_parsed_and_none_not_in_profile()
[Test] [Test]
public void should_allow_if_no_format_was_parsed_and_none_in_profile() 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); _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(CustomFormat.None.Name, _format1.Name, _format2.Name);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();

View File

@ -1,8 +1,15 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.CustomFormats; using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine.Specifications; 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.Profiles;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.CustomFormats; using NzbDrone.Core.Test.CustomFormats;
@ -11,13 +18,55 @@
namespace NzbDrone.Core.Test.DecisionEngineTests namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
[TestFixture] [TestFixture]
public class CutoffSpecificationFixture : CoreTest<UpgradableSpecification> public class CutoffSpecificationFixture : CoreTest<CutoffSpecification>
{ {
private CustomFormat _customFormat; private CustomFormat _customFormat;
private RemoteMovie _remoteMovie;
[SetUp] [SetUp]
public void 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() private void GivenCustomFormatHigher()
@ -30,73 +79,80 @@ private void GivenCustomFormatHigher()
[Test] [Test]
public void should_return_true_if_current_episode_is_less_than_cutoff() public void should_return_true_if_current_episode_is_less_than_cutoff()
{ {
Subject.CutoffNotMet(new Profile { Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }, GivenProfile(new Profile { Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() });
new QualityModel(Quality.DVD, new Revision(version: 2))).Should().BeTrue(); GivenFileQuality(new QualityModel(Quality.DVD, new Revision(version: 2)));
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();
} }
[Test] [Test]
public void should_return_false_if_current_episode_is_equal_to_cutoff() public void should_return_false_if_current_episode_is_equal_to_cutoff()
{ {
Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }, GivenProfile(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() });
new QualityModel(Quality.HDTV720p, new Revision(version: 2))).Should().BeFalse(); GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2)));
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
} }
[Test] [Test]
public void should_return_false_if_current_episode_is_greater_than_cutoff() public void should_return_false_if_current_episode_is_greater_than_cutoff()
{ {
Subject.CutoffNotMet(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }, GivenProfile(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() });
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); GivenFileQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2)));
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
} }
[Test] [Test]
public void should_return_true_when_new_episode_is_proper_but_existing_is_not() 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() }, GivenProfile(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() });
new QualityModel(Quality.HDTV720p, new Revision(version: 1)), GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 1)));
new QualityModel(Quality.HDTV720p, new Revision(version: 2))).Should().BeTrue(); GivenNewQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2)));
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();
} }
[Test] [Test]
public void should_return_false_if_cutoff_is_met_and_quality_is_higher() 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() }, GivenProfile(new Profile { Cutoff = Quality.HDTV720p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() });
new QualityModel(Quality.HDTV720p, new Revision(version: 2)), GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2)));
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2)));
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
} }
[Test] [Test]
public void should_return_false_if_custom_formats_is_met_and_quality_and_format_higher() public void should_return_false_if_custom_formats_is_met_and_quality_and_format_higher()
{ {
GivenProfile(new Profile
{
Cutoff = Quality.HDTV720p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatCutoff = CustomFormat.None.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems("None", "My Format")
});
GivenFileQuality(new QualityModel(Quality.HDTV720p));
GivenNewQuality(new QualityModel(Quality.Bluray1080p));
GivenCustomFormatHigher(); GivenCustomFormatHigher();
var old = new QualityModel(Quality.HDTV720p);
old.CustomFormats = new List<CustomFormat> { CustomFormat.None }; GivenOldCustomFormats(new List<CustomFormat> { CustomFormat.None });
var newQ = new QualityModel(Quality.Bluray1080p); GivenNewCustomFormats(new List<CustomFormat> { _customFormat });
newQ.CustomFormats = new List<CustomFormat> { _customFormat };
Subject.CutoffNotMet( Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
new Profile
{
Cutoff = Quality.HDTV720p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatCutoff = CustomFormat.None.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems("None", "My Format")
},
old,
newQ).Should().BeFalse();
} }
[Test] [Test]
public void should_return_true_if_cutoffs_are_met_but_is_a_revision_upgrade() 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, Cutoff = Quality.HDTV1080p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(), Items = Qualities.QualityFixture.GetDefaultQualities(),
}; });
Subject.CutoffNotMet( GivenFileQuality(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)));
profile, GivenNewQuality(new QualityModel(Quality.WEBDL1080p, new Revision(version: 2)));
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();
} }
} }
} }

View File

@ -1,9 +1,11 @@
using System; using System;
using System.Collections.Generic;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.DecisionEngine.Specifications.RssSync; using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
using NzbDrone.Core.History; using NzbDrone.Core.History;
@ -12,6 +14,7 @@
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.CustomFormats;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests namespace NzbDrone.Core.Test.DecisionEngineTests
@ -35,14 +38,23 @@ public void Setup()
Mocker.Resolve<UpgradableSpecification>(); Mocker.Resolve<UpgradableSpecification>();
_upgradeHistory = Mocker.Resolve<HistorySpecification>(); _upgradeHistory = Mocker.Resolve<HistorySpecification>();
CustomFormatsFixture.GivenCustomFormats(CustomFormat.None);
_fakeMovie = Builder<Movie>.CreateNew() _fakeMovie = Builder<Movie>.CreateNew()
.With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities() }) .With(c => c.Profile = new Profile
.Build(); {
Items = Qualities.QualityFixture.GetDefaultQualities(),
Cutoff = Quality.Bluray1080p.Id,
FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"),
FormatCutoff = CustomFormat.None.Id
})
.Build();
_parseResultSingle = new RemoteMovie _parseResultSingle = new RemoteMovie
{ {
Movie = _fakeMovie, 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)); _upgradableQuality = new QualityModel(Quality.SDTV, new Revision(version: 1));
@ -51,6 +63,10 @@ public void Setup()
Mocker.GetMock<IConfigService>() Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableCompletedDownloadHandling) .SetupGet(s => s.EnableCompletedDownloadHandling)
.Returns(true); .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) private void GivenMostRecentForEpisode(int episodeId, string downloadId, QualityModel quality, DateTime date, HistoryEventType eventType)
@ -142,10 +158,21 @@ public void should_be_not_upgradable_if_only_second_episodes_is_upgradable()
[Test] [Test]
public void should_not_be_upgradable_if_episode_is_of_same_quality_as_existing() 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)); _parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1));
_upgradableQuality = 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); GivenMostRecentForEpisode(FIRST_EPISODE_ID, string.Empty, _upgradableQuality, DateTime.UtcNow, HistoryEventType.Grabbed);
_upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); _upgradeHistory.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();
@ -154,7 +181,14 @@ public void should_not_be_upgradable_if_episode_is_of_same_quality_as_existing()
[Test] [Test]
public void should_not_be_upgradable_if_cutoff_already_met() 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)); _parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1));
_upgradableQuality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1)); _upgradableQuality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1));
@ -182,7 +216,14 @@ public void should_return_false_if_latest_history_has_a_download_id_and_cdh_is_d
public void should_return_false_if_cutoff_already_met_and_cdh_is_disabled() public void should_return_false_if_cutoff_already_met_and_cdh_is_disabled()
{ {
GivenCdhDisabled(); 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)); _parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 1));
_upgradableQuality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1)); _upgradableQuality = new QualityModel(Quality.WEBDL1080p, new Revision(version: 1));

View File

@ -60,6 +60,8 @@ private RemoteMovie GivenRemoteMovie(QualityModel quality, int age = 0, long siz
remoteMovie.Release.DownloadProtocol = downloadProtocol; remoteMovie.Release.DownloadProtocol = downloadProtocol;
remoteMovie.Release.Title = "A Movie 1998"; remoteMovie.Release.Title = "A Movie 1998";
remoteMovie.CustomFormats = new List<CustomFormat>();
return remoteMovie; return remoteMovie;
} }
@ -324,12 +326,12 @@ public void should_prefer_more_prioritized_words()
public void should_prefer_better_custom_format() public void should_prefer_better_custom_format()
{ {
var quality1 = new QualityModel(Quality.Bluray720p); var quality1 = new QualityModel(Quality.Bluray720p);
quality1.CustomFormats.Add(CustomFormat.None);
var remoteMovie1 = GivenRemoteMovie(quality1); var remoteMovie1 = GivenRemoteMovie(quality1);
remoteMovie1.CustomFormats.Add(CustomFormat.None);
var quality2 = new QualityModel(Quality.Bluray720p); var quality2 = new QualityModel(Quality.Bluray720p);
quality2.CustomFormats.Add(_customFormat1);
var remoteMovie2 = GivenRemoteMovie(quality2); var remoteMovie2 = GivenRemoteMovie(quality2);
remoteMovie2.CustomFormats.Add(_customFormat1);
var decisions = new List<DownloadDecision>(); var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteMovie1)); decisions.Add(new DownloadDecision(remoteMovie1));
@ -343,12 +345,12 @@ public void should_prefer_better_custom_format()
public void should_prefer_better_custom_format2() public void should_prefer_better_custom_format2()
{ {
var quality1 = new QualityModel(Quality.Bluray720p); var quality1 = new QualityModel(Quality.Bluray720p);
quality1.CustomFormats.Add(_customFormat1);
var remoteMovie1 = GivenRemoteMovie(quality1); var remoteMovie1 = GivenRemoteMovie(quality1);
remoteMovie1.CustomFormats.Add(_customFormat1);
var quality2 = new QualityModel(Quality.Bluray720p); var quality2 = new QualityModel(Quality.Bluray720p);
quality2.CustomFormats.Add(_customFormat2);
var remoteMovie2 = GivenRemoteMovie(quality2); var remoteMovie2 = GivenRemoteMovie(quality2);
remoteMovie2.CustomFormats.Add(_customFormat2);
var decisions = new List<DownloadDecision>(); var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteMovie1)); decisions.Add(new DownloadDecision(remoteMovie1));
@ -362,12 +364,12 @@ public void should_prefer_better_custom_format2()
public void should_prefer_2_custom_formats() public void should_prefer_2_custom_formats()
{ {
var quality1 = new QualityModel(Quality.Bluray720p); var quality1 = new QualityModel(Quality.Bluray720p);
quality1.CustomFormats.Add(_customFormat1);
var remoteMovie1 = GivenRemoteMovie(quality1); var remoteMovie1 = GivenRemoteMovie(quality1);
remoteMovie1.CustomFormats.Add(_customFormat1);
var quality2 = new QualityModel(Quality.Bluray720p); var quality2 = new QualityModel(Quality.Bluray720p);
quality2.CustomFormats.AddRange(new List<CustomFormat> { _customFormat1, _customFormat2 });
var remoteMovie2 = GivenRemoteMovie(quality2); var remoteMovie2 = GivenRemoteMovie(quality2);
remoteMovie2.CustomFormats.AddRange(new List<CustomFormat> { _customFormat1, _customFormat2 });
var decisions = new List<DownloadDecision>(); var decisions = new List<DownloadDecision>();
decisions.Add(new DownloadDecision(remoteMovie1)); decisions.Add(new DownloadDecision(remoteMovie1));

View File

@ -1,9 +1,12 @@
using FluentAssertions; using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.CustomFormats;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests namespace NzbDrone.Core.Test.DecisionEngineTests
@ -12,20 +15,39 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public class QualityUpgradeSpecificationFixture : CoreTest<UpgradableSpecification> 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 = public static object[] IsUpgradeTestCases =
{ {
new object[] { Quality.SDTV, 1, Quality.SDTV, 2, Quality.SDTV, true }, // Quality upgrade trumps custom format
new object[] { Quality.WEBDL720p, 1, Quality.WEBDL720p, 2, Quality.WEBDL720p, true }, new object[] { Quality.SDTV, 1, new List<CustomFormat>(), Quality.SDTV, 2, new List<CustomFormat>(), true },
new object[] { Quality.SDTV, 1, Quality.SDTV, 1, Quality.SDTV, false }, new object[] { Quality.SDTV, 1, new List<CustomFormat> { _customFormat1 }, Quality.SDTV, 2, new List<CustomFormat> { _customFormat1 }, true },
new object[] { Quality.WEBDL720p, 1, Quality.HDTV720p, 2, Quality.Bluray720p, false }, new object[] { Quality.SDTV, 1, new List<CustomFormat> { _customFormat1 }, Quality.SDTV, 2, new List<CustomFormat> { _customFormat2 }, true },
new object[] { Quality.WEBDL720p, 1, Quality.HDTV720p, 2, Quality.WEBDL720p, false }, new object[] { Quality.SDTV, 1, new List<CustomFormat> { _customFormat2 }, Quality.SDTV, 2, new List<CustomFormat> { _customFormat1 }, true },
new object[] { Quality.WEBDL720p, 1, Quality.WEBDL720p, 1, Quality.WEBDL720p, false },
new object[] { Quality.WEBDL1080p, 1, Quality.WEBDL1080p, 1, Quality.WEBDL1080p, false } // 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] [SetUp]
public void Setup() public void Setup()
{ {
CustomFormatsFixture.GivenCustomFormats(CustomFormat.None, _customFormat1, _customFormat2);
} }
private void GivenAutoDownloadPropers(bool autoDownloadPropers) private void GivenAutoDownloadPropers(bool autoDownloadPropers)
@ -37,13 +59,27 @@ private void GivenAutoDownloadPropers(bool autoDownloadPropers)
[Test] [Test]
[TestCaseSource("IsUpgradeTestCases")] [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); 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); .Should().Be(expected);
} }
@ -54,7 +90,11 @@ public void should_return_false_if_proper_and_autoDownloadPropers_is_false()
var profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() }; 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(); .Should().BeFalse();
} }
@ -69,7 +109,9 @@ public void should_return_false_if_release_and_existing_file_are_the_same()
Subject.IsUpgradable( Subject.IsUpgradable(
profile, profile,
new QualityModel(Quality.HDTV720p, new Revision(version: 1)), 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(); .Should().BeFalse();
} }
} }

View File

@ -2,13 +2,16 @@
using System.Linq; using System.Linq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Queue; using NzbDrone.Core.Queue;
using NzbDrone.Core.Test.CustomFormats;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests namespace NzbDrone.Core.Test.DecisionEngineTests
@ -26,10 +29,14 @@ public void Setup()
{ {
Mocker.Resolve<UpgradableSpecification>(); Mocker.Resolve<UpgradableSpecification>();
CustomFormatsFixture.GivenCustomFormats(CustomFormat.None);
_movie = Builder<Movie>.CreateNew() _movie = Builder<Movie>.CreateNew()
.With(e => e.Profile = new Profile .With(e => e.Profile = new Profile
{ {
Items = Qualities.QualityFixture.GetDefaultQualities(), Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"),
FormatCutoff = CustomFormat.None.Id,
UpgradeAllowed = true UpgradeAllowed = true
}) })
.Build(); .Build();
@ -39,9 +46,14 @@ public void Setup()
.Build(); .Build();
_remoteMovie = Builder<RemoteMovie>.CreateNew() _remoteMovie = Builder<RemoteMovie>.CreateNew()
.With(r => r.Movie = _movie) .With(r => r.Movie = _movie)
.With(r => r.ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.DVD) }) .With(r => r.ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.DVD) })
.Build(); .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() private void GivenEmptyQueue()
@ -87,12 +99,13 @@ public void should_return_true_when_quality_in_queue_is_lower()
_movie.Profile.Cutoff = Quality.Bluray1080p.Id; _movie.Profile.Cutoff = Quality.Bluray1080p.Id;
var remoteMovie = Builder<RemoteMovie>.CreateNew() var remoteMovie = Builder<RemoteMovie>.CreateNew()
.With(r => r.Movie = _movie) .With(r => r.Movie = _movie)
.With(r => r.ParsedMovieInfo = new ParsedMovieInfo .With(r => r.ParsedMovieInfo = new ParsedMovieInfo
{ {
Quality = new QualityModel(Quality.SDTV) Quality = new QualityModel(Quality.SDTV)
}) })
.Build(); .With(x => x.CustomFormats = new List<CustomFormat> { CustomFormat.None })
.Build();
GivenQueue(new List<RemoteMovie> { remoteMovie }); GivenQueue(new List<RemoteMovie> { remoteMovie });
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();

View File

@ -4,6 +4,7 @@
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.DecisionEngine.Specifications.RssSync; using NzbDrone.Core.DecisionEngine.Specifications.RssSync;
using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Download.Pending;
@ -75,7 +76,7 @@ private void GivenExistingFile(QualityModel quality)
private void GivenUpgradeForExistingFile() private void GivenUpgradeForExistingFile()
{ {
Mocker.GetMock<IUpgradableSpecification>() 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); .Returns(true);
} }

View File

@ -1,13 +1,17 @@
using System; using System;
using System.Collections.Generic;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.CustomFormats;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests namespace NzbDrone.Core.Test.DecisionEngineTests
@ -27,18 +31,30 @@ public void Setup()
Mocker.Resolve<UpgradableSpecification>(); Mocker.Resolve<UpgradableSpecification>();
_upgradeDisk = Mocker.Resolve<UpgradeDiskSpecification>(); _upgradeDisk = Mocker.Resolve<UpgradeDiskSpecification>();
CustomFormatsFixture.GivenCustomFormats(CustomFormat.None);
_firstFile = new MovieFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now }; _firstFile = new MovieFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now };
var fakeSeries = Builder<Movie>.CreateNew() 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
.With(e => e.MovieFile = _firstFile) {
.Build(); Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities(),
FormatItems = CustomFormatsFixture.GetSampleFormatItems("None"),
FormatCutoff = CustomFormat.None.Id
})
.With(e => e.MovieFile = _firstFile)
.Build();
_parseResultSingle = new RemoteMovie _parseResultSingle = new RemoteMovie
{ {
Movie = fakeSeries, Movie = fakeSeries,
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>()
}; };
Mocker.GetMock<ICustomFormatCalculationService>()
.Setup(x => x.ParseCustomFormat(It.IsAny<MovieFile>()))
.Returns(new List<CustomFormat>());
} }
private void WithFirstFileUpgradable() private void WithFirstFileUpgradable()
@ -63,6 +79,10 @@ public void should_be_upgradable_if_only_episode_is_upgradable()
[Test] [Test]
public void should_not_be_upgradable_if_qualities_are_the_same() 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); _firstFile.Quality = new QualityModel(Quality.WEBDL1080p);
_parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p); _parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p);
_upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); _upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse();

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Data;
using FluentMigrator; using FluentMigrator;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NUnit.Framework; using NUnit.Framework;
@ -11,18 +12,32 @@ namespace NzbDrone.Core.Test.Framework
public abstract class MigrationTest<TMigration> : DbTest public abstract class MigrationTest<TMigration> : DbTest
where TMigration : NzbDroneMigrationBase where TMigration : NzbDroneMigrationBase
{ {
protected long MigrationVersion protected long MigrationVersion => ((MigrationAttribute)Attribute.GetCustomAttribute(typeof(TMigration), typeof(MigrationAttribute))).Version;
[SetUp]
public override void SetupDb()
{ {
get SetupContainer();
{
var attrib = (MigrationAttribute)Attribute.GetCustomAttribute(typeof(TMigration), typeof(MigrationAttribute));
return attrib.Version;
}
} }
protected virtual IDirectDataMapper WithMigrationTestDb(Action<TMigration> beforeMigration = null) protected virtual IDirectDataMapper WithMigrationTestDb(Action<TMigration> beforeMigration = null)
{ {
var db = WithTestDb(new MigrationContext(MigrationType, MigrationVersion) return WithMigrationAction(beforeMigration).GetDirectDataMapper();
}
protected virtual IDbConnection WithDapperMigrationTestDb(Action<TMigration> beforeMigration = null)
{
return WithMigrationAction(beforeMigration).OpenConnection();
}
protected override void SetupLogging()
{
Mocker.SetConstant<ILoggerProvider>(Mocker.Resolve<MigrationLoggerProvider>());
}
private ITestDatabase WithMigrationAction(Action<TMigration> beforeMigration = null)
{
return WithTestDb(new MigrationContext(MigrationType, MigrationVersion)
{ {
BeforeMigration = m => BeforeMigration = m =>
{ {
@ -33,19 +48,6 @@ protected virtual IDirectDataMapper WithMigrationTestDb(Action<TMigration> befor
} }
} }
}); });
return db.GetDirectDataMapper();
}
protected override void SetupLogging()
{
Mocker.SetConstant<ILoggerProvider>(Mocker.Resolve<MigrationLoggerProvider>());
}
[SetUp]
public override void SetupDb()
{
SetupContainer();
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Linq; using System.Linq;
using Moq; using Moq;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
@ -21,6 +22,7 @@ void Update<T>(T childModel)
void Delete<T>(T childModel) void Delete<T>(T childModel)
where T : ModelBase, new(); where T : ModelBase, new();
IDirectDataMapper GetDirectDataMapper(); IDirectDataMapper GetDirectDataMapper();
IDbConnection OpenConnection();
} }
public class TestDatabase : ITestDatabase public class TestDatabase : ITestDatabase
@ -74,5 +76,10 @@ public IDirectDataMapper GetDirectDataMapper()
{ {
return new DirectDataMapper(_dbConnection); return new DirectDataMapper(_dbConnection);
} }
public IDbConnection OpenConnection()
{
return _dbConnection.OpenConnection();
}
} }
} }

View File

@ -7,6 +7,7 @@
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.MediaFiles.MovieImport; using NzbDrone.Core.MediaFiles.MovieImport;
@ -58,6 +59,10 @@ public void Setup()
.Setup(s => s.UpgradeMovieFile(It.IsAny<MovieFile>(), It.IsAny<LocalMovie>(), It.IsAny<bool>())) .Setup(s => s.UpgradeMovieFile(It.IsAny<MovieFile>(), It.IsAny<LocalMovie>(), It.IsAny<bool>()))
.Returns(new MovieFileMoveResult()); .Returns(new MovieFileMoveResult());
Mocker.GetMock<IHistoryService>()
.Setup(x => x.FindByDownloadId(It.IsAny<string>()))
.Returns(new List<History.History>());
_downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build(); _downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
} }

View File

@ -31,10 +31,10 @@ public void Setup()
.Returns(AugmentQualityResult.ResolutionOnly((int)Resolution.R1080p, Confidence.MediaInfo)); .Returns(AugmentQualityResult.ResolutionOnly((int)Resolution.R1080p, Confidence.MediaInfo));
_fileExtensionAugmenter.Setup(s => s.AugmentQuality(It.IsAny<LocalMovie>())) _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>())) _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) private void GivenAugmenters(params Mock<IAugmentQuality>[] mocks)

View File

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

View File

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Linq; using System.Linq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
@ -22,19 +23,23 @@ public void Setup()
{ {
_profileRepository = Mocker.Resolve<ProfileRepository>(); _profileRepository = Mocker.Resolve<ProfileRepository>();
Mocker.SetConstant<IProfileRepository>(_profileRepository); Mocker.SetConstant<IProfileRepository>(_profileRepository);
Mocker.GetMock<ICustomFormatService>()
.Setup(x => x.All())
.Returns(new List<CustomFormat>());
} }
[Test] [Test]
public void should_load_quality_profile() public void should_load_quality_profile()
{ {
var profile = new Profile var profile = new Profile
{ {
Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p), Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p),
FormatItems = CustomFormatsFixture.GetDefaultFormatItems(), FormatItems = CustomFormatsFixture.GetDefaultFormatItems(),
FormatCutoff = CustomFormat.None.Id, FormatCutoff = CustomFormat.None.Id,
Cutoff = Quality.Bluray1080p.Id, Cutoff = Quality.Bluray1080p.Id,
Name = "TestProfile" Name = "TestProfile"
}; };
_profileRepository.Insert(profile); _profileRepository.Insert(profile);

View File

@ -1,11 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Augmenters; using NzbDrone.Core.Parser.Augmenters;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests.AugmentersTests
{ {
@ -61,37 +59,6 @@ public void should_combine_languages()
result.Languages.Should().BeEquivalentTo(Language.English, Language.French); 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] [Test]
public void should_use_folder_release_group() public void should_use_folder_release_group()
{ {

View File

@ -1,10 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.CustomFormats;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Qualities namespace NzbDrone.Core.Test.Qualities
@ -14,11 +12,6 @@ public class QualityModelComparerFixture : CoreTest
{ {
public QualityModelComparer Subject { get; set; } public QualityModelComparer Subject { get; set; }
private CustomFormat _customFormat1;
private CustomFormat _customFormat2;
private CustomFormat _customFormat3;
private CustomFormat _customFormat4;
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
@ -78,18 +71,6 @@ private void GivenGroupedProfile()
Subject = new QualityModelComparer(profile); 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] [Test]
public void should_be_greater_when_first_quality_is_greater_than_second() public void should_be_greater_when_first_quality_is_greater_than_second()
{ {
@ -142,32 +123,6 @@ public void should_be_greater_when_using_a_custom_profile()
compare.Should().BeGreaterThan(0); 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] [Test]
public void should_ignore_group_order_by_default() public void should_ignore_group_order_by_default()
{ {
@ -193,57 +148,5 @@ public void should_respect_group_order()
compare.Should().BeLessThan(0); 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);
}
} }
} }

View File

@ -4,6 +4,7 @@
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Blacklisting namespace NzbDrone.Core.Blacklisting
@ -19,6 +20,7 @@ public class Blacklist : ModelBase
public long? Size { get; set; } public long? Size { get; set; }
public DownloadProtocol Protocol { get; set; } public DownloadProtocol Protocol { get; set; }
public string Indexer { get; set; } public string Indexer { get; set; }
public IndexerFlags IndexerFlags { get; set; }
public string Message { get; set; } public string Message { get; set; }
public string TorrentInfoHash { get; set; } public string TorrentInfoHash { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }

View File

@ -152,6 +152,11 @@ public void Handle(DownloadFailedEvent message)
Languages = message.Languages Languages = message.Languages
}; };
if (Enum.TryParse(message.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags))
{
blacklist.IndexerFlags = flags;
}
_blacklistRepository.Insert(blacklist); _blacklistRepository.Insert(blacklist);
} }

View File

@ -7,10 +7,6 @@ namespace NzbDrone.Core.CustomFormats
{ {
public class CustomFormat : ModelBase, IEquatable<CustomFormat> public class CustomFormat : ModelBase, IEquatable<CustomFormat>
{ {
public string Name { get; set; }
public List<FormatTag> FormatTags { get; set; }
public CustomFormat() public CustomFormat()
{ {
} }
@ -21,18 +17,25 @@ public CustomFormat(string name, params string[] tags)
FormatTags = tags.Select(t => new FormatTag(t)).ToList(); 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() public override string ToString()
{ {
return Name; return Name;
} }
public static CustomFormat None => new CustomFormat("None");
public bool Equals(CustomFormat other) public bool Equals(CustomFormat other)
{ {
if (ReferenceEquals(null, other)) if (other is null)
{ {
return false; return false;
} }
@ -47,7 +50,7 @@ public bool Equals(CustomFormat other)
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if (ReferenceEquals(null, obj)) if (obj is null)
{ {
return false; return false;
} }
@ -67,26 +70,7 @@ public override bool Equals(object obj)
public override int GetHashCode() public override int GetHashCode()
{ {
return Id.GetHashCode(); return Id;
}
}
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 };
} }
} }
} }

View File

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

View File

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

View File

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

View File

@ -3,11 +3,11 @@
namespace NzbDrone.Core.CustomFormats 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) public CustomFormatRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator) : base(database, eventAggregator)

View File

@ -1,14 +1,9 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.Composition; using NzbDrone.Core.CustomFormats.Events;
using NzbDrone.Core.Blacklisting;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.History; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Profiles;
namespace NzbDrone.Core.CustomFormats namespace NzbDrone.Core.CustomFormats
{ {
@ -24,145 +19,21 @@ public interface ICustomFormatService
public class CustomFormatService : ICustomFormatService public class CustomFormatService : ICustomFormatService
{ {
private readonly ICustomFormatRepository _formatRepository; private readonly ICustomFormatRepository _formatRepository;
private readonly IHistoryService _historyService; private readonly IEventAggregator _eventAggregator;
private IProfileService _profileService;
public IProfileService ProfileService
{
get
{
if (_profileService == null)
{
_profileService = _container.Resolve<IProfileService>();
}
return _profileService;
}
}
private readonly IContainer _container;
private readonly ICached<Dictionary<int, CustomFormat>> _cache; private readonly ICached<Dictionary<int, CustomFormat>> _cache;
private readonly Logger _logger;
public static Dictionary<int, CustomFormat> AllCustomFormats;
public CustomFormatService(ICustomFormatRepository formatRepository, public CustomFormatService(ICustomFormatRepository formatRepository,
ICacheManager cacheManager, ICacheManager cacheManager,
IContainer container, IEventAggregator eventAggregator)
IHistoryService historyService,
Logger logger)
{ {
_formatRepository = formatRepository; _formatRepository = formatRepository;
_container = container; _eventAggregator = eventAggregator;
_cache = cacheManager.GetCache<Dictionary<int, CustomFormat>>(typeof(CustomFormat), "formats"); _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)
{
_formatRepository.Update(customFormat);
_cache.Clear();
}
public CustomFormat Insert(CustomFormat customFormat)
{
var ret = _formatRepository.Insert(customFormat);
try
{
ProfileService.AddCustomFormat(ret);
}
catch (Exception e)
{
_logger.Error(e, "Failure while trying to add the new custom format to all profiles. Deleting again!");
_formatRepository.Delete(ret);
throw;
}
_cache.Clear();
return ret;
}
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)
{
_logger.Error(e, "Failed to delete format with id {} from other repositories! Format will not be deleted!", id);
throw;
}
//Finally delete the format for real!
_formatRepository.Delete(id);
_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()
{
var allItems = repository.All();
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());
});
repository.UpdateMany(toUpdate.ToList());
} }
private Dictionary<int, CustomFormat> AllDictionary() private Dictionary<int, CustomFormat> AllDictionary()
{ {
return _cache.Get("all", () => return _cache.Get("all", () => _formatRepository.All().ToDictionary(m => m.Id));
{
var all = _formatRepository.All().Select(x => (CustomFormat)x).ToDictionary(m => m.Id);
AllCustomFormats = all;
return all;
});
} }
public List<CustomFormat> All() public List<CustomFormat> All()
@ -175,11 +46,35 @@ public CustomFormat GetById(int id)
return AllDictionary()[id]; return AllDictionary()[id];
} }
public static Dictionary<string, List<CustomFormat>> Templates public void Update(CustomFormat customFormat)
{ {
get _formatRepository.Update(customFormat);
{ _cache.Clear();
return new Dictionary<string, List<CustomFormat>> }
public CustomFormat Insert(CustomFormat customFormat)
{
// Add to DB then insert into profiles
var result = _formatRepository.Insert(customFormat);
_cache.Clear();
_eventAggregator.PublishEvent(new CustomFormatAddedEvent(result));
return result;
}
public void Delete(int id)
{
var format = _formatRepository.Get(id);
// Remove from profiles before removing from DB
_eventAggregator.PublishEvent(new CustomFormatDeletedEvent(format));
_formatRepository.Delete(id);
_cache.Clear();
}
public static Dictionary<string, List<CustomFormat>> Templates => new Dictionary<string, List<CustomFormat>>
{ {
{ {
"Easy", new List<CustomFormat> "Easy", new List<CustomFormat>
@ -207,7 +102,5 @@ public static Dictionary<string, List<CustomFormat>> Templates
} }
} }
}; };
}
}
} }
} }

View File

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

View File

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

View File

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

View File

@ -7,20 +7,21 @@
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.CustomFormats namespace NzbDrone.Core.CustomFormats
{ {
public class FormatTag 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 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); 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) public FormatTag(string raw)
{ {
Raw = raw; Raw = raw;
@ -31,13 +32,13 @@ public FormatTag(string raw)
throw new ArgumentException("Quality Tag is not in the correct format!"); throw new ArgumentException("Quality Tag is not in the correct format!");
} }
ParseRawMatch(match); ParseFormatTagString(match);
} }
// This function is needed for json deserialization to work. public string Raw { get; set; }
private FormatTag() public TagType TagType { get; set; }
{ public TagModifier TagModifier { get; set; }
} public object Value { get; set; }
public bool DoesItMatch(ParsedMovieInfo movieInfo) public bool DoesItMatch(ParsedMovieInfo movieInfo)
{ {
@ -50,59 +51,64 @@ public bool DoesItMatch(ParsedMovieInfo movieInfo)
return match; return match;
} }
private bool MatchString(string compared)
{
if (compared == null)
{
return false;
}
if (TagModifier.HasFlag(TagModifier.Regex))
{
var regexValue = (Regex)Value;
return regexValue.IsMatch(compared);
}
else
{
var stringValue = (string)Value;
return compared.ToLower().Contains(stringValue.Replace(" ", string.Empty).ToLower());
}
}
private bool DoesItMatchWithoutMods(ParsedMovieInfo movieInfo) private bool DoesItMatchWithoutMods(ParsedMovieInfo movieInfo)
{ {
if (movieInfo == null)
{
return false;
}
var filename = (string)movieInfo?.ExtraInfo?.GetValueOrDefault("Filename");
switch (TagType) switch (TagType)
{ {
case TagType.Edition: case TagType.Edition:
return MatchString(movieInfo.Edition);
case TagType.Custom: case TagType.Custom:
string compared = null; return MatchString(movieInfo.SimpleReleaseTitle) || MatchString(filename);
if (TagType == TagType.Custom)
{
compared = movieInfo.SimpleReleaseTitle;
}
else
{
compared = movieInfo.Edition;
}
if (TagModifier.HasFlag(TagModifier.Regex))
{
Regex regexValue = (Regex)Value;
return regexValue.IsMatch(compared);
}
else
{
string stringValue = (string)Value;
return compared.ToLower().Contains(stringValue.Replace(" ", string.Empty).ToLower());
}
case TagType.Language: case TagType.Language:
return movieInfo.Languages.Contains((Language)Value); return movieInfo?.Languages?.Contains((Language)Value) ?? false;
case TagType.Resolution: 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: case TagType.Modifier:
return movieInfo.Quality.Quality.Modifier == (Modifier)Value; return (movieInfo?.Quality?.Quality?.Modifier ?? (int)Modifier.NONE) == (Modifier)Value;
case TagType.Source: case TagType.Source:
return movieInfo.Quality.Quality.Source == (Source)Value; return (movieInfo?.Quality?.Quality?.Source ?? (int)Source.UNKNOWN) == (Source)Value;
case TagType.Size: 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); var tuple = Value as (long, long)? ?? (0, 0);
return size > tuple.Item1 && size < tuple.Item2; return size > tuple.Item1 && size < tuple.Item2;
case TagType.Indexer: case TagType.Indexer:
#if !LIBRARY #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 #endif
default: default:
return false; 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) if (match.Groups["m_re"].Success)
{ {
TagModifier |= TagModifier.AbsolutelyRequired; TagModifier |= TagModifier.AbsolutelyRequired;
@ -117,137 +123,169 @@ private void ParseRawMatch(Match match)
{ {
TagModifier |= TagModifier.Not; TagModifier |= TagModifier.Not;
} }
}
private void ParseResolutionType(string value)
{
TagType = TagType.Resolution;
switch (value)
{
case "2160":
Value = Resolution.R2160p;
break;
case "1080":
Value = Resolution.R1080p;
break;
case "720":
Value = Resolution.R720p;
break;
case "576":
Value = Resolution.R576p;
break;
case "480":
Value = Resolution.R480p;
break;
default:
break;
}
}
private void ParseSourceType(string value)
{
TagType = TagType.Source;
switch (value)
{
case "cam":
Value = Source.CAM;
break;
case "telesync":
Value = Source.TELESYNC;
break;
case "telecine":
Value = Source.TELECINE;
break;
case "workprint":
Value = Source.WORKPRINT;
break;
case "dvd":
Value = Source.DVD;
break;
case "tv":
Value = Source.TV;
break;
case "webdl":
Value = Source.WEBDL;
break;
case "bluray":
Value = Source.BLURAY;
break;
default:
break;
}
}
private void ParseModifierType(string value)
{
TagType = TagType.Modifier;
switch (value)
{
case "regional":
Value = Modifier.REGIONAL;
break;
case "screener":
Value = Modifier.SCREENER;
break;
case "rawhd":
Value = Modifier.RAWHD;
break;
case "brdisk":
Value = Modifier.BRDISK;
break;
case "remux":
Value = Modifier.REMUX;
break;
default:
break;
}
}
private void ParseIndexerFlagType(string value)
{
TagType = TagType.Indexer;
var flagValues = Enum.GetValues(typeof(IndexerFlags));
foreach (IndexerFlags flagValue in flagValues)
{
var flagString = flagValue.ToString();
if (flagString.ToLower().Replace("_", string.Empty) != value.ToLower().Replace("_", string.Empty))
{
continue;
}
Value = flagValue;
break;
}
}
private void ParseSizeType(string value)
{
TagType = TagType.Size;
var matches = SizeTagRegex.Match(value);
var min = double.Parse(matches.Groups["min"].Value, CultureInfo.InvariantCulture);
var max = double.Parse(matches.Groups["max"].Value, CultureInfo.InvariantCulture);
Value = (min.Gigabytes(), max.Gigabytes());
}
private void ParseString(string value)
{
if (TagModifier.HasFlag(TagModifier.Regex))
{
Value = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
else
{
Value = value;
}
}
private void ParseFormatTagString(Match match)
{
ParseTagModifier(match);
var type = match.Groups["type"].Value.ToLower();
var value = match.Groups["value"].Value.ToLower();
switch (type) switch (type)
{ {
case "r": case "r":
TagType = TagType.Resolution; ParseResolutionType(value);
switch (value)
{
case "2160":
Value = Resolution.R2160p;
break;
case "1080":
Value = Resolution.R1080p;
break;
case "720":
Value = Resolution.R720p;
break;
case "576":
Value = Resolution.R576p;
break;
case "480":
Value = Resolution.R480p;
break;
}
break; break;
case "s": case "s":
TagType = TagType.Source; ParseSourceType(value);
switch (value)
{
case "cam":
Value = Source.CAM;
break;
case "telesync":
Value = Source.TELESYNC;
break;
case "telecine":
Value = Source.TELECINE;
break;
case "workprint":
Value = Source.WORKPRINT;
break;
case "dvd":
Value = Source.DVD;
break;
case "tv":
Value = Source.TV;
break;
case "webdl":
Value = Source.WEBDL;
break;
case "bluray":
Value = Source.BLURAY;
break;
}
break; break;
case "m": case "m":
TagType = TagType.Modifier; ParseModifierType(value);
switch (value)
{
case "regional":
Value = Modifier.REGIONAL;
break;
case "screener":
Value = Modifier.SCREENER;
break;
case "rawhd":
Value = Modifier.RAWHD;
break;
case "brdisk":
Value = Modifier.BRDISK;
break;
case "remux":
Value = Modifier.REMUX;
break;
}
break; break;
case "e": case "e":
TagType = TagType.Edition; TagType = TagType.Edition;
if (TagModifier.HasFlag(TagModifier.Regex)) ParseString(value);
{
Value = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
else
{
Value = value;
}
break; break;
case "l": case "l":
TagType = TagType.Language; TagType = TagType.Language;
Value = Parser.LanguageParser.ParseLanguages(value).First(); Value = LanguageParser.ParseLanguages(value).First();
break; break;
case "i": case "i":
#if !LIBRARY #if !LIBRARY
TagType = TagType.Indexer; ParseIndexerFlagType(value);
var flagValues = Enum.GetValues(typeof(IndexerFlags));
foreach (IndexerFlags flagValue in flagValues)
{
var flagString = flagValue.ToString();
if (flagString.ToLower().Replace("_", string.Empty) != value.ToLower().Replace("_", string.Empty))
{
continue;
}
Value = flagValue;
break;
}
#endif #endif
break; break;
case "g": case "g":
TagType = TagType.Size; ParseSizeType(value);
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; break;
case "c": case "c":
default: default:
TagType = TagType.Custom; TagType = TagType.Custom;
if (TagModifier.HasFlag(TagModifier.Regex)) ParseString(value);
{
Value = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
else
{
Value = value;
}
break; break;
} }
} }
@ -272,28 +310,4 @@ public enum TagModifier
Not = 2, // Do not match Not = 2, // Do not match
AbsolutelyRequired = 4 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
}
} }

View File

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

View File

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

View File

@ -1,52 +1,15 @@
using System; using System;
using System.Data;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Dapper;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.CustomFormats; using NzbDrone.Core.CustomFormats;
namespace NzbDrone.Core.Datastore.Converters 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 class CustomFormatIntConverter : JsonConverter<CustomFormat>
{ {
public override CustomFormat Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override CustomFormat Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
var val = reader.GetInt32(); return new CustomFormat { Id = reader.GetInt32() };
if (val == 0)
{
return CustomFormat.None;
}
return CustomFormatService.AllCustomFormats[val];
} }
public override void Write(Utf8JsonWriter writer, CustomFormat value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, CustomFormat value, JsonSerializerOptions options)

View File

@ -14,7 +14,7 @@ public EmbeddedDocumentConverter()
var serializerSettings = new JsonSerializerOptions var serializerSettings = new JsonSerializerOptions
{ {
AllowTrailingCommas = true, AllowTrailingCommas = true,
IgnoreNullValues = false, IgnoreNullValues = true,
PropertyNameCaseInsensitive = true, PropertyNameCaseInsensitive = true,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNamingPolicy = JsonNamingPolicy.CamelCase,

View File

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

View File

@ -111,7 +111,7 @@ public static void Map()
.Ignore(d => d.GroupName) .Ignore(d => d.GroupName)
.Ignore(d => d.Weight); .Ignore(d => d.Weight);
Mapper.Entity<CustomFormatDefinition>("CustomFormats").RegisterModel(); Mapper.Entity<CustomFormat>("CustomFormats").RegisterModel();
Mapper.Entity<Profile>("Profiles").RegisterModel(); Mapper.Entity<Profile>("Profiles").RegisterModel();
Mapper.Entity<Log>("Logs").RegisterModel(); Mapper.Entity<Log>("Logs").RegisterModel();
@ -147,11 +147,10 @@ private static void RegisterMappers()
SqlMapper.RemoveTypeMap(typeof(DateTime)); SqlMapper.RemoveTypeMap(typeof(DateTime));
SqlMapper.AddTypeHandler(new DapperUtcConverter()); SqlMapper.AddTypeHandler(new DapperUtcConverter());
SqlMapper.AddTypeHandler(new DapperQualityIntConverter()); SqlMapper.AddTypeHandler(new DapperQualityIntConverter());
SqlMapper.AddTypeHandler(new DapperCustomFormatIntConverter());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfileQualityItem>>(new QualityIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfileQualityItem>>(new QualityIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfileFormatItem>>(new CustomFormatIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfileFormatItem>>(new CustomFormatIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<FormatTag>>(new QualityTagStringConverter())); 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<Dictionary<string, string>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<IDictionary<string, string>>()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<IDictionary<string, string>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>());
@ -160,7 +159,7 @@ private static void RegisterMappers()
SqlMapper.AddTypeHandler(new DapperLanguageIntConverter()); SqlMapper.AddTypeHandler(new DapperLanguageIntConverter());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<Language>>(new LanguageIntConverter())); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<Language>>(new LanguageIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<string>>()); 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<ReleaseInfo>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<HashSet<int>>()); SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<HashSet<int>>());
SqlMapper.AddTypeHandler(new OsPathConverter()); SqlMapper.AddTypeHandler(new OsPathConverter());

View File

@ -2,11 +2,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine namespace NzbDrone.Core.DecisionEngine
{ {
@ -49,12 +47,6 @@ private int CompareBy<TSubject, TValue>(TSubject left, TSubject right, Func<TSub
return leftValue.CompareTo(rightValue); 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) private int CompareAll(params int[] comparers)
{ {
return comparers.Select(comparer => comparer).FirstOrDefault(result => result != 0); return comparers.Select(comparer => comparer).FirstOrDefault(result => result != 0);
@ -63,23 +55,9 @@ private int CompareAll(params int[] comparers)
private int CompareQuality(DownloadDecision x, DownloadDecision y) private int CompareQuality(DownloadDecision x, DownloadDecision y)
{ {
return CompareAll(CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.Movie.Profile.GetIndex(remoteMovie.ParsedMovieInfo.Quality.Quality)), 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.Real),
CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.ParsedMovieInfo.Quality.Revision.Version)); 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) private int ComparePreferredWords(DownloadDecision x, DownloadDecision y)

View File

@ -6,6 +6,7 @@
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
@ -26,19 +27,19 @@ public class DownloadDecisionMaker : IMakeDownloadDecision
private readonly IEnumerable<IDecisionEngineSpecification> _specifications; private readonly IEnumerable<IDecisionEngineSpecification> _specifications;
private readonly IParsingService _parsingService; private readonly IParsingService _parsingService;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly IQualityDefinitionService _definitionService; private readonly ICustomFormatCalculationService _formatCalculator;
private readonly Logger _logger; private readonly Logger _logger;
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications, public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications,
IParsingService parsingService, IParsingService parsingService,
IConfigService configService, IConfigService configService,
IQualityDefinitionService qualityDefinitionService, ICustomFormatCalculationService formatCalculator,
Logger logger) Logger logger)
{ {
_specifications = specifications; _specifications = specifications;
_parsingService = parsingService; _parsingService = parsingService;
_configService = configService; _configService = configService;
_definitionService = qualityDefinitionService; _formatCalculator = formatCalculator;
_logger = logger; _logger = logger;
} }
@ -106,7 +107,7 @@ private IEnumerable<DownloadDecision> GetDecisions(List<ReleaseInfo> reports, Se
result.ReleaseName = report.Title; result.ReleaseName = report.Title;
var remoteMovie = result.RemoteMovie; var remoteMovie = result.RemoteMovie;
remoteMovie.CustomFormats = _formatCalculator.ParseCustomFormat(parsedMovieInfo);
remoteMovie.Release = report; remoteMovie.Release = report;
remoteMovie.MappingResult = result.MappingResultType; remoteMovie.MappingResult = result.MappingResultType;

View File

@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats; using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -20,14 +22,14 @@ public CustomFormatAllowedbyProfileSpecification(Logger logger)
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
{ {
var formats = subject.ParsedMovieInfo.Quality.CustomFormats.WithNone(); var formats = subject.CustomFormats.Any() ? subject.CustomFormats : new List<CustomFormat> { CustomFormat.None };
_logger.Debug("Checking if report meets custom format requirements. {0}", formats.ToExtendedString()); _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 notAllowedFormats = subject.Movie.Profile.FormatItems.Where(v => v.Allowed == false).Select(f => f.Format).ToList();
var notWantedFormats = notAllowedFormats.Intersect(formats); var notWantedFormats = notAllowedFormats.Intersect(formats);
if (notWantedFormats.Any()) if (notWantedFormats.Any())
{ {
_logger.Debug("Custom Formats {0} rejected by Movie's 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.ToExtendedString()); return Decision.Reject("Custom Formats {0} not wanted in profile", notWantedFormats.ConcatToString());
} }
return Decision.Accept(); return Decision.Accept();

View File

@ -1,4 +1,6 @@
using NLog; using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -6,12 +8,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
public class CutoffSpecification : IDecisionEngineSpecification public class CutoffSpecification : IDecisionEngineSpecification
{ {
private readonly UpgradableSpecification _qualityUpgradableSpecification; private readonly IUpgradableSpecification _upgradableSpecification;
private readonly ICustomFormatCalculationService _formatService;
private readonly Logger _logger; 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; _logger = logger;
} }
@ -21,17 +27,25 @@ public CutoffSpecification(UpgradableSpecification qualityUpgradableSpecificatio
public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria)
{ {
var profile = subject.Movie.Profile; var profile = subject.Movie.Profile;
var file = subject.Movie.MovieFile;
if (subject.Movie.MovieFile != null) if (file != null)
{ {
if (!_qualityUpgradableSpecification.CutoffNotMet(profile, file.Movie = subject.Movie;
subject.Movie.MovieFile.Quality, var customFormats = _formatService.ParseCustomFormat(file);
subject.ParsedMovieInfo.Quality))
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 qualityCutoffIndex = profile.GetIndex(profile.Cutoff);
var qualityCutoff = profile.Items[qualityCutoffIndex.Index]; 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);
} }
} }

View File

@ -1,5 +1,7 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Queue; using NzbDrone.Core.Queue;
@ -9,15 +11,18 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public class QueueSpecification : IDecisionEngineSpecification public class QueueSpecification : IDecisionEngineSpecification
{ {
private readonly IQueueService _queueService; private readonly IQueueService _queueService;
private readonly UpgradableSpecification _qualityUpgradableSpecification; private readonly UpgradableSpecification _upgradableSpecification;
private readonly ICustomFormatCalculationService _formatService;
private readonly Logger _logger; private readonly Logger _logger;
public QueueSpecification(IQueueService queueService, public QueueSpecification(IQueueService queueService,
UpgradableSpecification qualityUpgradableSpecification, UpgradableSpecification upgradableSpecification,
Logger logger) ICustomFormatCalculationService formatService,
Logger logger)
{ {
_queueService = queueService; _queueService = queueService;
_qualityUpgradableSpecification = qualityUpgradableSpecification; _upgradableSpecification = upgradableSpecification;
_formatService = formatService;
_logger = logger; _logger = logger;
} }
@ -36,25 +41,38 @@ public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCrit
var remoteMovie = queueItem.RemoteMovie; var remoteMovie = queueItem.RemoteMovie;
var qualityProfile = subject.Movie.Profile; 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); 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); _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); 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); _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, 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"); return Decision.Reject("Another release is queued and the Quality profile does not allow upgrades");
} }

View File

@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -12,16 +13,19 @@ public class DelaySpecification : IDecisionEngineSpecification
{ {
private readonly IPendingReleaseService _pendingReleaseService; private readonly IPendingReleaseService _pendingReleaseService;
private readonly IUpgradableSpecification _qualityUpgradableSpecification; private readonly IUpgradableSpecification _qualityUpgradableSpecification;
private readonly ICustomFormatCalculationService _formatService;
private readonly IDelayProfileService _delayProfileService; private readonly IDelayProfileService _delayProfileService;
private readonly Logger _logger; private readonly Logger _logger;
public DelaySpecification(IPendingReleaseService pendingReleaseService, public DelaySpecification(IPendingReleaseService pendingReleaseService,
IUpgradableSpecification qualityUpgradableSpecification, IUpgradableSpecification qualityUpgradableSpecification,
ICustomFormatCalculationService formatService,
IDelayProfileService delayProfileService, IDelayProfileService delayProfileService,
Logger logger) Logger logger)
{ {
_pendingReleaseService = pendingReleaseService; _pendingReleaseService = pendingReleaseService;
_qualityUpgradableSpecification = qualityUpgradableSpecification; _qualityUpgradableSpecification = qualityUpgradableSpecification;
_formatService = formatService;
_delayProfileService = delayProfileService; _delayProfileService = delayProfileService;
_logger = logger; _logger = logger;
} }
@ -65,9 +69,16 @@ public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase se
var comparer = new QualityModelComparer(profile); 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) if (upgradable)
{ {

View File

@ -2,6 +2,7 @@
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.History; using NzbDrone.Core.History;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -11,17 +12,20 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
public class HistorySpecification : IDecisionEngineSpecification public class HistorySpecification : IDecisionEngineSpecification
{ {
private readonly IHistoryService _historyService; private readonly IHistoryService _historyService;
private readonly UpgradableSpecification _qualityUpgradableSpecification; private readonly UpgradableSpecification _upgradableSpecification;
private readonly ICustomFormatCalculationService _formatService;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly Logger _logger; private readonly Logger _logger;
public HistorySpecification(IHistoryService historyService, public HistorySpecification(IHistoryService historyService,
UpgradableSpecification qualityUpgradableSpecification, UpgradableSpecification upgradableSpecification,
IConfigService configService, ICustomFormatCalculationService formatService,
Logger logger) IConfigService configService,
Logger logger)
{ {
_historyService = historyService; _historyService = historyService;
_qualityUpgradableSpecification = qualityUpgradableSpecification; _upgradableSpecification = upgradableSpecification;
_formatService = formatService;
_configService = configService; _configService = configService;
_logger = logger; _logger = logger;
} }
@ -45,10 +49,20 @@ public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase se
if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed) if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed)
{ {
var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12)); var customFormats = _formatService.ParseCustomFormat(mostRecent);
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 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) if (!recent && cdhEnabled)
{ {
return Decision.Accept(); return Decision.Accept();

View File

@ -1,5 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
@ -7,10 +11,11 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
public interface IUpgradableSpecification public interface IUpgradableSpecification
{ {
bool IsUpgradable(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, QualityModel newQuality = null); 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 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 public class UpgradableSpecification : IUpgradableSpecification
@ -24,38 +29,51 @@ public UpgradableSpecification(IConfigService configService, Logger logger)
_logger = logger; _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);
int compare = new QualityModelComparer(profile).Compare(newQuality, currentQuality);
if (compare <= 0) if (qualityCompare > 0)
{
return false;
}
if (IsRevisionUpgrade(currentQuality, newQuality))
{
_logger.Debug("New item has a better quality revision");
return true;
}
}
_logger.Debug("New item has a better quality");
return true;
}
public bool CutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null)
{
var comparer = new QualityModelComparer(profile);
var cutoffCompare = comparer.Compare(currentQuality.Quality.Id, profile.Cutoff);
if (cutoffCompare < 0)
{ {
_logger.Debug("New item has a better quality");
return true; return true;
} }
if (comparer.Compare(currentQuality.CustomFormats, profile.FormatCutoff) < 0) if (qualityCompare < 0)
{
_logger.Debug("Existing item has better quality, skipping");
return false;
}
// Accept unless the user doesn't want to prefer propers, optionally they can
// use preferred words to prefer propers/repacks over non-propers/repacks.
if (_configService.AutoDownloadPropers &&
newQuality?.Revision.CompareTo(currentQuality.Revision) > 0)
{
_logger.Debug("New item has a better quality revision");
return true;
}
var customFormatCompare = new CustomFormatsComparer(profile).Compare(newCustomFormats, currentCustomFormats);
if (customFormatCompare <= 0)
{
_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 custom format upgrade");
return true;
}
public bool QualityCutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null)
{
var cutoffCompare = new QualityModelComparer(profile).Compare(currentQuality.Quality.Id, profile.Cutoff);
if (cutoffCompare < 0)
{ {
return true; return true;
} }
@ -68,6 +86,24 @@ public bool CutoffNotMet(Profile profile, QualityModel currentQuality, QualityMo
return false; 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;
}
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) public bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality)
{ {
var compare = newQuality.Revision.CompareTo(currentQuality.Revision); var compare = newQuality.Revision.CompareTo(currentQuality.Revision);
@ -81,11 +117,12 @@ public bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuali
return false; 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 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"); _logger.Debug("Quality profile allows upgrading");
return true; return true;

View File

@ -1,4 +1,6 @@
using NLog; using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -7,11 +9,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public class UpgradeAllowedSpecification : IDecisionEngineSpecification public class UpgradeAllowedSpecification : IDecisionEngineSpecification
{ {
private readonly UpgradableSpecification _upgradableSpecification; private readonly UpgradableSpecification _upgradableSpecification;
private readonly ICustomFormatCalculationService _formatService;
private readonly Logger _logger; private readonly Logger _logger;
public UpgradeAllowedSpecification(UpgradableSpecification upgradableSpecification, Logger logger) public UpgradeAllowedSpecification(UpgradableSpecification upgradableSpecification,
ICustomFormatCalculationService formatService,
Logger logger)
{ {
_upgradableSpecification = upgradableSpecification; _upgradableSpecification = upgradableSpecification;
_formatService = formatService;
_logger = logger; _logger = logger;
} }
@ -32,11 +38,15 @@ public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase se
return Decision.Accept(); 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, if (!_upgradableSpecification.IsUpgradeAllowed(qualityProfile,
file.Quality, file.Quality,
subject.ParsedMovieInfo.Quality)) customFormats,
subject.ParsedMovieInfo.Quality,
subject.CustomFormats))
{ {
_logger.Debug("Upgrading is not allowed by the quality profile"); _logger.Debug("Upgrading is not allowed by the quality profile");

View File

@ -1,4 +1,6 @@
using NLog; using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -7,11 +9,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public class UpgradeDiskSpecification : IDecisionEngineSpecification public class UpgradeDiskSpecification : IDecisionEngineSpecification
{ {
private readonly UpgradableSpecification _qualityUpgradableSpecification; private readonly UpgradableSpecification _qualityUpgradableSpecification;
private readonly ICustomFormatCalculationService _formatService;
private readonly Logger _logger; private readonly Logger _logger;
public UpgradeDiskSpecification(UpgradableSpecification qualityUpgradableSpecification, Logger logger) public UpgradeDiskSpecification(UpgradableSpecification qualityUpgradableSpecification,
ICustomFormatCalculationService formatService,
Logger logger)
{ {
_qualityUpgradableSpecification = qualityUpgradableSpecification; _qualityUpgradableSpecification = qualityUpgradableSpecification;
_formatService = formatService;
_logger = logger; _logger = logger;
} }
@ -25,12 +31,19 @@ public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase se
return Decision.Accept(); return Decision.Accept();
} }
var profile = subject.Movie.Profile;
var file = subject.Movie.MovieFile; 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(); return Decision.Accept();

View File

@ -5,6 +5,7 @@
using NzbDrone.Common.Crypto; using NzbDrone.Common.Crypto;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Jobs; using NzbDrone.Core.Jobs;
@ -43,18 +44,20 @@ public class PendingReleaseService : IPendingReleaseService,
private readonly IDelayProfileService _delayProfileService; private readonly IDelayProfileService _delayProfileService;
private readonly ITaskManager _taskManager; private readonly ITaskManager _taskManager;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
public PendingReleaseService(IIndexerStatusService indexerStatusService, public PendingReleaseService(IIndexerStatusService indexerStatusService,
IPendingReleaseRepository repository, IPendingReleaseRepository repository,
IMovieService movieService, IMovieService movieService,
IParsingService parsingService, IParsingService parsingService,
IDelayProfileService delayProfileService, IDelayProfileService delayProfileService,
ITaskManager taskManager, ITaskManager taskManager,
IConfigService configService, IConfigService configService,
IEventAggregator eventAggregator, ICustomFormatCalculationService formatCalculator,
Logger logger) IEventAggregator eventAggregator,
Logger logger)
{ {
_indexerStatusService = indexerStatusService; _indexerStatusService = indexerStatusService;
_repository = repository; _repository = repository;
@ -63,6 +66,7 @@ public PendingReleaseService(IIndexerStatusService indexerStatusService,
_delayProfileService = delayProfileService; _delayProfileService = delayProfileService;
_taskManager = taskManager; _taskManager = taskManager;
_configService = configService; _configService = configService;
_formatCalculator = formatCalculator;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
} }
@ -159,6 +163,8 @@ public List<RemoteMovie> GetPendingRemoteMovies(int movieId)
{ {
if (pendingRelease.RemoteMovie != null) if (pendingRelease.RemoteMovie != null)
{ {
pendingRelease.RemoteMovie.CustomFormats = _formatCalculator.ParseCustomFormat(pendingRelease.ParsedMovieInfo);
var ect = pendingRelease.Release.PublishDate.AddMinutes(GetDelay(pendingRelease.RemoteMovie)); var ect = pendingRelease.Release.PublishDate.AddMinutes(GetDelay(pendingRelease.RemoteMovie));
if (ect < nextRssSync.Value) if (ect < nextRssSync.Value)

View File

@ -5,6 +5,7 @@
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.History; using NzbDrone.Core.History;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
@ -27,20 +28,23 @@ public class TrackedDownloadService : ITrackedDownloadService
private readonly IHistoryService _historyService; private readonly IHistoryService _historyService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IConfigService _config; private readonly IConfigService _config;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly Logger _logger; private readonly Logger _logger;
private readonly ICached<TrackedDownload> _cache; private readonly ICached<TrackedDownload> _cache;
public TrackedDownloadService(IParsingService parsingService, public TrackedDownloadService(IParsingService parsingService,
ICacheManager cacheManager, ICacheManager cacheManager,
IHistoryService historyService, IHistoryService historyService,
IConfigService config, IConfigService config,
IEventAggregator eventAggregator, ICustomFormatCalculationService formatCalculator,
Logger logger) IEventAggregator eventAggregator,
Logger logger)
{ {
_parsingService = parsingService; _parsingService = parsingService;
_historyService = historyService; _historyService = historyService;
_cache = cacheManager.GetCache<TrackedDownload>(GetType()); _cache = cacheManager.GetCache<TrackedDownload>(GetType());
_config = config; _config = config;
_formatCalculator = formatCalculator;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
} }
@ -129,6 +133,12 @@ public TrackedDownload TrackDownload(DownloadClientDefinition downloadClient, Do
} }
} }
// 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 // 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) if (trackedDownload.RemoteMovie == null)
{ {

View File

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

View File

@ -5,6 +5,7 @@
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
@ -18,6 +19,7 @@ public class MovieFile : ModelBase
public DateTime DateAdded { get; set; } public DateTime DateAdded { get; set; }
public string SceneName { get; set; } public string SceneName { get; set; }
public string ReleaseGroup { get; set; } public string ReleaseGroup { get; set; }
public IndexerFlags IndexerFlags { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
public MediaInfoModel MediaInfo { get; set; } public MediaInfoModel MediaInfo { get; set; }

View File

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality; using NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
@ -33,7 +32,6 @@ public LocalMovie Aggregate(LocalMovie localMovie, bool otherFiles)
var modifier = Modifier.NONE; var modifier = Modifier.NONE;
var modifierConfidence = Confidence.Default; var modifierConfidence = Confidence.Default;
var revison = new Revision(); var revison = new Revision();
var customFormats = new List<CustomFormat>();
foreach (var augmentedQuality in augmentedQualities) foreach (var augmentedQuality in augmentedQualities)
{ {
@ -62,18 +60,11 @@ public LocalMovie Aggregate(LocalMovie localMovie, bool otherFiles)
{ {
revison = augmentedQuality.Revision; 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); _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) if (resolutionConfidence == Confidence.MediaInfo)
{ {

View File

@ -19,8 +19,7 @@ public AugmentQualityResult AugmentQuality(LocalMovie localMovie)
Confidence.Tag, Confidence.Tag,
quality.Quality.Modifier, quality.Quality.Modifier,
Confidence.Tag, Confidence.Tag,
quality.Revision, quality.Revision);
quality.CustomFormats);
} }
} }
} }

View File

@ -24,8 +24,7 @@ public AugmentQualityResult AugmentQuality(LocalMovie localMovie)
confidence, confidence,
quality.Quality.Modifier, quality.Quality.Modifier,
confidence, confidence,
quality.Revision, quality.Revision);
quality.CustomFormats);
} }
} }
} }

View File

@ -19,8 +19,7 @@ public AugmentQualityResult AugmentQuality(LocalMovie localMovie)
Confidence.Tag, Confidence.Tag,
quality.Quality.Modifier, quality.Quality.Modifier,
Confidence.Tag, Confidence.Tag,
quality.Revision, quality.Revision);
quality.CustomFormats);
} }
} }
} }

View File

@ -1,5 +1,3 @@
using System.Collections.Generic;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators.Augmenters.Quality
@ -14,16 +12,13 @@ public class AugmentQualityResult
public Confidence ModifierConfidence { get; set; } public Confidence ModifierConfidence { get; set; }
public Revision Revision { get; set; } public Revision Revision { get; set; }
public List<CustomFormat> CustomFormats { get; set; }
public AugmentQualityResult(Source source, public AugmentQualityResult(Source source,
Confidence sourceConfidence, Confidence sourceConfidence,
int resolution, int resolution,
Confidence resolutionConfidence, Confidence resolutionConfidence,
Modifier modifier, Modifier modifier,
Confidence modifierConfidence, Confidence modifierConfidence,
Revision revision, Revision revision)
List<CustomFormat> customFormats)
{ {
Source = source; Source = source;
SourceConfidence = sourceConfidence; SourceConfidence = sourceConfidence;
@ -32,22 +27,21 @@ public AugmentQualityResult(Source source,
Modifier = modifier; Modifier = modifier;
ModifierConfidence = modifierConfidence; ModifierConfidence = modifierConfidence;
Revision = revision; Revision = revision;
CustomFormats = customFormats;
} }
public static AugmentQualityResult SourceOnly(Source source, Confidence sourceConfidence) 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) 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) 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);
} }
} }
} }

View File

@ -7,6 +7,7 @@
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Extras; using NzbDrone.Core.Extras;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
@ -26,20 +27,23 @@ public class ImportApprovedMovie : IImportApprovedMovie
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IExtraService _extraService; private readonly IExtraService _extraService;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IHistoryService _historyService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
public ImportApprovedMovie(IUpgradeMediaFiles movieFileUpgrader, public ImportApprovedMovie(IUpgradeMediaFiles movieFileUpgrader,
IMediaFileService mediaFileService, IMediaFileService mediaFileService,
IExtraService extraService, IExtraService extraService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IEventAggregator eventAggregator, IHistoryService historyService,
Logger logger) IEventAggregator eventAggregator,
Logger logger)
{ {
_movieFileUpgrader = movieFileUpgrader; _movieFileUpgrader = movieFileUpgrader;
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_extraService = extraService; _extraService = extraService;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_historyService = historyService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
} }
@ -85,6 +89,18 @@ public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownloa
movieFile.ReleaseGroup = localMovie.ReleaseGroup; movieFile.ReleaseGroup = localMovie.ReleaseGroup;
movieFile.Edition = localMovie.Edition; 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; bool copyOnly;
switch (importMode) switch (importMode)
{ {

View File

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

View File

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

View File

@ -30,12 +30,6 @@ public ParsedMovieInfo AugmentMovieInfo(ParsedMovieInfo movieInfo, object helper
movieInfo.Edition = otherInfo.Edition; 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()) if (otherInfo.ReleaseGroup.IsNotNullOrWhiteSpace() && movieInfo.ReleaseGroup.IsNullOrWhiteSpace())
{ {
movieInfo.ReleaseGroup = otherInfo.ReleaseGroup; movieInfo.ReleaseGroup = otherInfo.ReleaseGroup;

View File

@ -10,14 +10,14 @@ public class ParsedMovieInfo
public string MovieTitle { get; set; } public string MovieTitle { get; set; }
public string SimpleReleaseTitle { get; set; } public string SimpleReleaseTitle { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
[JsonIgnore] public List<Language> Languages { get; set; } = new List<Language>();
public Dictionary<string, object> ExtraInfo = new Dictionary<string, object>();
public List<Language> Languages = new List<Language>();
public string ReleaseGroup { get; set; } public string ReleaseGroup { get; set; }
public string ReleaseHash { get; set; } public string ReleaseHash { get; set; }
public string Edition { get; set; } public string Edition { get; set; }
public int Year { get; set; } public int Year { get; set; }
public string ImdbId { get; set; } public string ImdbId { get; set; }
[JsonIgnore]
public Dictionary<string, object> ExtraInfo { get; set; } = new Dictionary<string, object>();
public override string ToString() public override string ToString()
{ {

View File

@ -1,3 +1,5 @@
using System.Collections.Generic;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Download.Clients; using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
@ -7,6 +9,7 @@ public class RemoteMovie
{ {
public ReleaseInfo Release { get; set; } public ReleaseInfo Release { get; set; }
public ParsedMovieInfo ParsedMovieInfo { get; set; } public ParsedMovieInfo ParsedMovieInfo { get; set; }
public List<CustomFormat> CustomFormats { get; set; }
public Movie Movie { get; set; } public Movie Movie { get; set; }
public MappingResultType MappingResult { get; set; } public MappingResultType MappingResult { get; set; }
public bool DownloadAllowed { get; set; } public bool DownloadAllowed { get; set; }

View File

@ -385,7 +385,7 @@ public static string CleanSeriesTitle(this string title)
return ReplaceGermanUmlauts(NormalizeRegex.Replace(title, string.Empty).ToLower()).RemoveAccent(); 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 = SpecialEpisodeWordRegex.Replace(title, string.Empty);
title = PunctuationRegex.Replace(title, " "); title = PunctuationRegex.Replace(title, " ");
@ -395,7 +395,7 @@ public static string NormalizeEpisodeTitle(string title)
.ToLower(); .ToLower();
} }
public static string NormalizeTitle(string title) public static string NormalizeTitle(this string title)
{ {
title = WordDelimiterRegex.Replace(title, " "); title = WordDelimiterRegex.Replace(title, " ");
title = PunctuationRegex.Replace(title, string.Empty); title = PunctuationRegex.Replace(title, string.Empty);
@ -406,6 +406,11 @@ public static string NormalizeTitle(string title)
return title.Trim().ToLower(); return title.Trim().ToLower();
} }
public static string SimplifyReleaseTitle(this string title)
{
return SimpleReleaseTitleRegex.Replace(title, string.Empty);
}
public static string ParseReleaseGroup(string title) public static string ParseReleaseGroup(string title)
{ {
title = title.Trim(); title = title.Trim();

View File

@ -3,9 +3,7 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
@ -13,7 +11,6 @@
using NzbDrone.Core.Parser.Augmenters; using NzbDrone.Core.Parser.Augmenters;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Parser.RomanNumerals; using NzbDrone.Core.Parser.RomanNumerals;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.Parser namespace NzbDrone.Core.Parser
{ {
@ -25,31 +22,24 @@ public interface IParsingService
ParsedMovieInfo EnhanceMovieInfo(ParsedMovieInfo parsedMovieInfo, List<object> helpers = null); ParsedMovieInfo EnhanceMovieInfo(ParsedMovieInfo parsedMovieInfo, List<object> helpers = null);
ParsedMovieInfo ParseMinimalMovieInfo(string path, bool isDir = false); ParsedMovieInfo ParseMinimalMovieInfo(string path, bool isDir = false);
ParsedMovieInfo ParseMinimalPathMovieInfo(string path); ParsedMovieInfo ParseMinimalPathMovieInfo(string path);
List<FormatTagMatchResult> MatchFormatTags(ParsedMovieInfo movieInfo);
} }
public class ParsingService : IParsingService public class ParsingService : IParsingService
{ {
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; private static HashSet<ArabicRomanNumeral> _arabicRomanNumeralMappings;
public ParsingService( private readonly IMovieService _movieService;
IMovieService movieService, private readonly IConfigService _config;
private readonly IEnumerable<IAugmentParsedMovieInfo> _augmenters;
private readonly Logger _logger;
public ParsingService(IMovieService movieService,
IConfigService configService, IConfigService configService,
IQualityDefinitionService qualityDefinitionService,
ICustomFormatService formatService,
IEnumerable<IAugmentParsedMovieInfo> augmenters, IEnumerable<IAugmentParsedMovieInfo> augmenters,
Logger logger) Logger logger)
{ {
_movieService = movieService; _movieService = movieService;
_config = configService; _config = configService;
_qualityDefinitionService = qualityDefinitionService;
_formatService = formatService;
_augmenters = augmenters; _augmenters = augmenters;
_logger = logger; _logger = logger;
@ -80,15 +70,6 @@ public ParsedMovieInfo EnhanceMovieInfo(ParsedMovieInfo minimalInfo, List<object
minimalInfo = AugmentMovieInfo(minimalInfo, helpers); 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; return minimalInfo;
} }
@ -105,41 +86,6 @@ private ParsedMovieInfo AugmentMovieInfo(ParsedMovieInfo minimalInfo, List<objec
return minimalInfo; 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) public ParsedMovieInfo ParseMinimalMovieInfo(string file, bool isDir = false)
{ {
return Parser.ParseMovieTitle(file, _config.ParsingLeniency > 0, isDir); return Parser.ParseMovieTitle(file, _config.ParsingLeniency > 0, isDir);

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
@ -73,5 +74,11 @@ public QualityIndex GetIndex(int id)
return new QualityIndex(); 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();
}
} }
} }

View File

@ -1,3 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Dapper;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@ -10,9 +14,34 @@ public interface IProfileRepository : IBasicRepository<Profile>
public class ProfileRepository : BasicRepository<Profile>, IProfileRepository 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) : 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) public bool Exists(int id)

View File

@ -2,6 +2,7 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.CustomFormats; using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.CustomFormats.Events;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@ -15,8 +16,6 @@ public interface IProfileService
{ {
Profile Add(Profile profile); Profile Add(Profile profile);
void Update(Profile profile); void Update(Profile profile);
void AddCustomFormat(CustomFormat format);
void DeleteCustomFormat(int formatId);
void Delete(int id); void Delete(int id);
List<Profile> All(); List<Profile> All();
Profile Get(int id); Profile Get(int id);
@ -24,24 +23,27 @@ public interface IProfileService
Profile GetDefaultProfile(string name, Quality cutoff = null, params Quality[] allowed); 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 IProfileRepository _profileRepository;
private readonly ICustomFormatService _formatService;
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly INetImportFactory _netImportFactory; private readonly INetImportFactory _netImportFactory;
private readonly ICustomFormatService _formatService;
private readonly Logger _logger; private readonly Logger _logger;
public ProfileService(IProfileRepository profileRepository, public ProfileService(IProfileRepository profileRepository,
IMovieService movieService, ICustomFormatService formatService,
INetImportFactory netImportFactory, IMovieService movieService,
ICustomFormatService formatService, INetImportFactory netImportFactory,
Logger logger) Logger logger)
{ {
_profileRepository = profileRepository; _profileRepository = profileRepository;
_formatService = formatService;
_movieService = movieService; _movieService = movieService;
_netImportFactory = netImportFactory; _netImportFactory = netImportFactory;
_formatService = formatService;
_logger = logger; _logger = logger;
} }
@ -55,36 +57,6 @@ public void Update(Profile profile)
_profileRepository.Update(profile); _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) public void Delete(int id)
{ {
if (_movieService.GetAllMovies().Any(c => c.ProfileId == id) || _netImportFactory.All().Any(c => c.ProfileId == id)) if (_movieService.GetAllMovies().Any(c => c.ProfileId == id) || _netImportFactory.All().Any(c => c.ProfileId == id))
@ -110,10 +82,38 @@ public bool Exists(int id)
return _profileRepository.Exists(id); 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) 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()) if (All().Any())
{ {
return; return;
@ -207,9 +207,7 @@ public void Handle(ApplicationStartedEvent message)
public Profile GetDefaultProfile(string name, Quality cutoff = null, params Quality[] allowed) public Profile GetDefaultProfile(string name, Quality cutoff = null, params Quality[] allowed)
{ {
var groupedQualites = Quality.DefaultQualityDefinitions.GroupBy(q => q.Weight); var groupedQualites = Quality.DefaultQualityDefinitions.GroupBy(q => q.Weight);
var formats = _formatService.All();
var items = new List<ProfileQualityItem>(); var items = new List<ProfileQualityItem>();
var formatItems = new List<ProfileFormatItem>();
var groupId = 1000; var groupId = 1000;
var profileCutoff = cutoff == null ? Quality.Unknown.Id : cutoff.Id; var profileCutoff = cutoff == null ? Quality.Unknown.Id : cutoff.Id;
@ -245,15 +243,20 @@ public Profile GetDefaultProfile(string name, Quality cutoff = null, params Qual
groupId++; groupId++;
} }
foreach (var format in formats) var formatItems = new List<ProfileFormatItem>
{ {
formatItems.Add(new ProfileFormatItem new ProfileFormatItem
{ {
Id = format.Id, Id = 0,
Format = format, Allowed = true,
Allowed = false Format = CustomFormat.None
}); }
} }.Concat(_formatService.All().Select(format => new ProfileFormatItem
{
Id = format.Id,
Allowed = false,
Format = format
})).ToList();
var qualityProfile = new Profile var qualityProfile = new Profile
{ {
@ -262,19 +265,9 @@ public Profile GetDefaultProfile(string name, Quality cutoff = null, params Qual
Items = items, Items = items,
Language = Language.English, Language = Language.English,
FormatCutoff = CustomFormat.None.Id, FormatCutoff = CustomFormat.None.Id,
FormatItems = new List<ProfileFormatItem> FormatItems = formatItems
{
new ProfileFormatItem
{
Id = 0,
Allowed = true,
Format = CustomFormat.None
}
}
}; };
qualityProfile.FormatItems.AddRange(formatItems);
return qualityProfile; return qualityProfile;
} }

View File

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

View File

@ -1,7 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Qualities namespace NzbDrone.Core.Qualities
@ -10,8 +8,6 @@ public class QualityModel : IEmbeddedDocument, IEquatable<QualityModel>
{ {
public Quality Quality { get; set; } public Quality Quality { get; set; }
public List<CustomFormat> CustomFormats { get; set; }
public Revision Revision { get; set; } public Revision Revision { get; set; }
public string HardcodedSubs { get; set; } public string HardcodedSubs { get; set; }
@ -24,16 +20,15 @@ public QualityModel()
{ {
} }
public QualityModel(Quality quality, Revision revision = null, List<CustomFormat> customFormats = null) public QualityModel(Quality quality, Revision revision = null)
{ {
Quality = quality; Quality = quality;
Revision = revision ?? new Revision(); Revision = revision ?? new Revision();
CustomFormats = customFormats ?? new List<CustomFormat>();
} }
public override string ToString() 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() public override int GetHashCode()

View File

@ -1,12 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Profiles; using NzbDrone.Core.Profiles;
namespace NzbDrone.Core.Qualities 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; private readonly Profile _profile;
@ -50,49 +48,10 @@ public int Compare(QualityModel left, QualityModel right, bool respectGroupOrder
if (result == 0) if (result == 0)
{ {
result = Compare(left.CustomFormats, right.CustomFormats); result = left.Revision.CompareTo(right.Revision);
if (result == 0)
{
result = left.Revision.CompareTo(right.Revision);
}
} }
return result; 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();
}
} }
} }

View File

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

View File

@ -6,7 +6,6 @@
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.Composition; using NzbDrone.Common.Composition;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Download.TrackedDownloads;
@ -38,11 +37,6 @@ public void SetUp()
// set up a dummy broadcaster to allow tests to resolve // set up a dummy broadcaster to allow tests to resolve
var mockBroadcaster = new Mock<IBroadcastSignalRMessage>(); var mockBroadcaster = new Mock<IBroadcastSignalRMessage>();
_container.Register<IBroadcastSignalRMessage>(mockBroadcaster.Object); _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] [Test]

View File

@ -1,4 +1,5 @@
using NzbDrone.Core.Blacklisting; using NzbDrone.Core.Blacklisting;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using Radarr.Http; using Radarr.Http;
@ -7,10 +8,14 @@ namespace Radarr.Api.V3.Blacklist
public class BlacklistModule : RadarrRestModule<BlacklistResource> public class BlacklistModule : RadarrRestModule<BlacklistResource>
{ {
private readonly IBlacklistService _blacklistService; private readonly IBlacklistService _blacklistService;
private readonly ICustomFormatCalculationService _formatCalculator;
public BlacklistModule(IBlacklistService blacklistService) public BlacklistModule(IBlacklistService blacklistService,
ICustomFormatCalculationService formatCalculator)
{ {
_blacklistService = blacklistService; _blacklistService = blacklistService;
_formatCalculator = formatCalculator;
GetResourcePaged = GetBlacklist; GetResourcePaged = GetBlacklist;
DeleteResource = DeleteBlacklist; DeleteResource = DeleteBlacklist;
} }
@ -19,7 +24,7 @@ private PagingResource<BlacklistResource> GetBlacklist(PagingResource<BlacklistR
{ {
var pagingSpec = pagingResource.MapToPagingSpec<BlacklistResource, NzbDrone.Core.Blacklisting.Blacklist>("date", SortDirection.Descending); 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) private void DeleteBlacklist(int id)

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using Radarr.Api.V3.CustomFormats;
using Radarr.Api.V3.Movies; using Radarr.Api.V3.Movies;
using Radarr.Http.REST; using Radarr.Http.REST;
@ -14,6 +16,7 @@ public class BlacklistResource : RestResource
public string SourceTitle { get; set; } public string SourceTitle { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public List<CustomFormatResource> CustomFormats { get; set; }
public DateTime Date { get; set; } public DateTime Date { get; set; }
public DownloadProtocol Protocol { get; set; } public DownloadProtocol Protocol { get; set; }
public string Indexer { get; set; } public string Indexer { get; set; }
@ -24,7 +27,7 @@ public class BlacklistResource : RestResource
public static class BlacklistResourceMapper 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) if (model == null)
{ {
@ -39,6 +42,7 @@ public static BlacklistResource MapToResource(this NzbDrone.Core.Blacklisting.Bl
SourceTitle = model.SourceTitle, SourceTitle = model.SourceTitle,
Languages = model.Languages, Languages = model.Languages,
Quality = model.Quality, Quality = model.Quality,
CustomFormats = formatCalculator.ParseCustomFormat(model).ToResource(),
Date = model.Date, Date = model.Date,
Protocol = model.Protocol, Protocol = model.Protocol,
Indexer = model.Indexer, Indexer = model.Indexer,

View File

@ -6,16 +6,20 @@
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using Radarr.Http; using Radarr.Http;
namespace Radarr.Api.V3.Qualities namespace Radarr.Api.V3.CustomFormats
{ {
public class CustomFormatModule : RadarrRestModule<CustomFormatResource> public class CustomFormatModule : RadarrRestModule<CustomFormatResource>
{ {
private readonly ICustomFormatService _formatService; private readonly ICustomFormatService _formatService;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly IParsingService _parsingService; private readonly IParsingService _parsingService;
public CustomFormatModule(ICustomFormatService formatService, IParsingService parsingService) public CustomFormatModule(ICustomFormatService formatService,
ICustomFormatCalculationService formatCalculator,
IParsingService parsingService)
{ {
_formatService = formatService; _formatService = formatService;
_formatCalculator = formatCalculator;
_parsingService = parsingService; _parsingService = parsingService;
SharedValidator.RuleFor(c => c.Name).NotEmpty(); SharedValidator.RuleFor(c => c.Name).NotEmpty();
@ -31,7 +35,7 @@ public CustomFormatModule(ICustomFormatService formatService, IParsingService pa
var allNewTags = c.Split(',').Select(t => t.ToLower()); var allNewTags = c.Split(',').Select(t => t.ToLower());
var enumerable = allTags.ToList(); var enumerable = allTags.ToList();
var newTags = allNewTags.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."); .WithMessage("Should be unique.");
@ -103,8 +107,8 @@ private CustomFormatTestResource Test()
return new CustomFormatTestResource return new CustomFormatTestResource
{ {
Matches = _parsingService.MatchFormatTags(parsed).ToResource(), Matches = _formatCalculator.MatchFormatTags(parsed).ToResource(),
MatchedFormats = parsed.Quality.CustomFormats.ToResource() MatchedFormats = _formatCalculator.ParseCustomFormat(parsed).ToResource()
}; };
} }
@ -125,8 +129,8 @@ private CustomFormatTestResource TestWithNewModel()
return new CustomFormatTestResource return new CustomFormatTestResource
{ {
Matches = _parsingService.MatchFormatTags(parsed).ToResource(), Matches = _formatCalculator.MatchFormatTags(parsed).ToResource(),
MatchedFormats = parsed.Quality.CustomFormats.ToResource() MatchedFormats = _formatCalculator.ParseCustomFormat(parsed).ToResource()
}; };
} }
} }

View File

@ -1,12 +1,15 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
using NzbDrone.Core.CustomFormats; using NzbDrone.Core.CustomFormats;
using Radarr.Http.REST; using Radarr.Http.REST;
namespace Radarr.Api.V3.Qualities namespace Radarr.Api.V3.CustomFormats
{ {
public class CustomFormatResource : RestResource public class CustomFormatResource : RestResource
{ {
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
public override int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string FormatTags { get; set; } public string FormatTags { get; set; }
public string Simplicity { get; set; } public string Simplicity { get; set; }
@ -24,8 +27,18 @@ public static CustomFormatResource ToResource(this CustomFormat model)
}; };
} }
public static List<CustomFormatResource> ToResource(this IEnumerable<CustomFormat> models)
{
return models.Select(m => m.ToResource()).ToList();
}
public static CustomFormat ToModel(this CustomFormatResource resource) public static CustomFormat ToModel(this CustomFormatResource resource)
{ {
if (resource.Id == 0 && resource.Name == "None")
{
return CustomFormat.None;
}
return new CustomFormat return new CustomFormat
{ {
Id = resource.Id, Id = resource.Id,
@ -33,10 +46,5 @@ public static CustomFormat ToModel(this CustomFormatResource resource)
FormatTags = resource.FormatTags.Split(',').Select(s => new FormatTag(s)).ToList() 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();
}
} }
} }

View File

@ -4,9 +4,9 @@
using NzbDrone.Core.CustomFormats; using NzbDrone.Core.CustomFormats;
using Radarr.Http.REST; 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 CustomFormatResource CustomFormat { get; set; }
public List<FormatTagGroupMatchesResource> GroupMatches { get; set; } public List<FormatTagGroupMatchesResource> GroupMatches { get; set; }
@ -21,27 +21,27 @@ public class FormatTagGroupMatchesResource : RestResource
public class CustomFormatTestResource : RestResource public class CustomFormatTestResource : RestResource
{ {
public List<FormatTagMatchResultResource> Matches { get; set; } public List<CustomFormatMatchResultResource> Matches { get; set; }
public List<CustomFormatResource> MatchedFormats { get; set; } public List<CustomFormatResource> MatchedFormats { get; set; }
} }
public static class QualityTagMatchResultResourceMapper public static class QualityTagMatchResultResourceMapper
{ {
public static FormatTagMatchResultResource ToResource(this FormatTagMatchResult model) public static CustomFormatMatchResultResource ToResource(this CustomFormatMatchResult model)
{ {
if (model == null) if (model == null)
{ {
return null; return null;
} }
return new FormatTagMatchResultResource return new CustomFormatMatchResultResource
{ {
CustomFormat = model.CustomFormat.ToResource(), CustomFormat = model.CustomFormat.ToResource(),
GroupMatches = model.GroupMatches.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(); return models.Select(ToResource).ToList();
} }

View File

@ -3,7 +3,7 @@
using FluentValidation.Validators; using FluentValidation.Validators;
using NzbDrone.Core.CustomFormats; using NzbDrone.Core.CustomFormats;
namespace Radarr.Api.V3.Qualities namespace Radarr.Api.V3.CustomFormats
{ {
public class FormatTagValidator : PropertyValidator public class FormatTagValidator : PropertyValidator
{ {
@ -24,7 +24,7 @@ protected override bool IsValid(PropertyValidatorContext context)
var invalidTags = tags.Where(t => !FormatTag.QualityTagRegex.IsMatch(t)); var invalidTags = tags.Where(t => !FormatTag.QualityTagRegex.IsMatch(t));
if (invalidTags.Count() == 0) if (!invalidTags.Any())
{ {
return true; return true;
} }

View File

@ -2,10 +2,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Nancy; using Nancy;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.History; using NzbDrone.Core.History;
using NzbDrone.Core.Movies;
using Radarr.Api.V3.Movies; using Radarr.Api.V3.Movies;
using Radarr.Http; using Radarr.Http;
using Radarr.Http.Extensions; using Radarr.Http.Extensions;
@ -16,14 +18,20 @@ namespace Radarr.Api.V3.History
public class HistoryModule : RadarrRestModule<HistoryResource> public class HistoryModule : RadarrRestModule<HistoryResource>
{ {
private readonly IHistoryService _historyService; private readonly IHistoryService _historyService;
private readonly IMovieService _movieService;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly IUpgradableSpecification _upgradableSpecification; private readonly IUpgradableSpecification _upgradableSpecification;
private readonly IFailedDownloadService _failedDownloadService; private readonly IFailedDownloadService _failedDownloadService;
public HistoryModule(IHistoryService historyService, public HistoryModule(IHistoryService historyService,
IMovieService movieService,
ICustomFormatCalculationService formatCalculator,
IUpgradableSpecification upgradableSpecification, IUpgradableSpecification upgradableSpecification,
IFailedDownloadService failedDownloadService) IFailedDownloadService failedDownloadService)
{ {
_historyService = historyService; _historyService = historyService;
_movieService = movieService;
_formatCalculator = formatCalculator;
_upgradableSpecification = upgradableSpecification; _upgradableSpecification = upgradableSpecification;
_failedDownloadService = failedDownloadService; _failedDownloadService = failedDownloadService;
GetResourcePaged = GetHistory; GetResourcePaged = GetHistory;
@ -35,7 +43,12 @@ public HistoryModule(IHistoryService historyService,
protected HistoryResource MapToResource(NzbDrone.Core.History.History model, bool includeMovie) 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) if (includeMovie)
{ {
@ -44,7 +57,7 @@ protected HistoryResource MapToResource(NzbDrone.Core.History.History model, boo
if (model.Movie != null) if (model.Movie != null)
{ {
resource.QualityCutoffNotMet = _upgradableSpecification.CutoffNotMet(model.Movie.Profile, model.Quality); resource.QualityCutoffNotMet = _upgradableSpecification.QualityCutoffNotMet(model.Movie.Profile, model.Quality);
} }
return resource; return resource;

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.History; using NzbDrone.Core.History;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using Radarr.Api.V3.CustomFormats;
using Radarr.Api.V3.Movies; using Radarr.Api.V3.Movies;
using Radarr.Http.REST; using Radarr.Http.REST;
@ -14,6 +16,7 @@ public class HistoryResource : RestResource
public string SourceTitle { get; set; } public string SourceTitle { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public List<CustomFormatResource> CustomFormats { get; set; }
public bool QualityCutoffNotMet { get; set; } public bool QualityCutoffNotMet { get; set; }
public DateTime Date { get; set; } public DateTime Date { get; set; }
public string DownloadId { get; set; } public string DownloadId { get; set; }
@ -27,7 +30,7 @@ public class HistoryResource : RestResource
public static class HistoryResourceMapper 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) if (model == null)
{ {
@ -42,6 +45,7 @@ public static HistoryResource ToResource(this NzbDrone.Core.History.History mode
SourceTitle = model.SourceTitle, SourceTitle = model.SourceTitle,
Languages = model.Languages, Languages = model.Languages,
Quality = model.Quality, Quality = model.Quality,
CustomFormats = formatCalculator.ParseCustomFormat(model).ToResource(),
//QualityCutoffNotMet //QualityCutoffNotMet
Date = model.Date, Date = model.Date,

View File

@ -7,6 +7,7 @@
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using Radarr.Api.V3.CustomFormats;
using Radarr.Http.REST; using Radarr.Http.REST;
namespace Radarr.Api.V3.Indexers namespace Radarr.Api.V3.Indexers
@ -15,6 +16,7 @@ public class ReleaseResource : RestResource
{ {
public string Guid { get; set; } public string Guid { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public List<CustomFormatResource> CustomFormats { get; set; }
public int QualityWeight { get; set; } public int QualityWeight { get; set; }
public int Age { get; set; } public int Age { get; set; }
public double AgeHours { get; set; } public double AgeHours { get; set; }
@ -68,6 +70,7 @@ public static ReleaseResource ToResource(this DownloadDecision model)
{ {
Guid = releaseInfo.Guid, Guid = releaseInfo.Guid,
Quality = parsedMovieInfo.Quality, Quality = parsedMovieInfo.Quality,
CustomFormats = remoteMovie.CustomFormats.ToResource(),
//QualityWeight //QualityWeight
Age = releaseInfo.Age, Age = releaseInfo.Age,

View File

@ -4,6 +4,7 @@
using System.Linq; using System.Linq;
using Nancy; using Nancy;
using NLog; using NLog;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
@ -11,6 +12,7 @@
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using Radarr.Api.V3.CustomFormats;
using Radarr.Http; using Radarr.Http;
using Radarr.Http.Extensions; using Radarr.Http.Extensions;
using BadRequestException = Radarr.Http.REST.BadRequestException; using BadRequestException = Radarr.Http.REST.BadRequestException;
@ -24,6 +26,7 @@ public class MovieFileModule : RadarrRestModuleWithSignalR<MovieFileResource, Mo
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IRecycleBinProvider _recycleBinProvider; private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IMovieService _movieService; private readonly IMovieService _movieService;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly IUpgradableSpecification _qualityUpgradableSpecification; private readonly IUpgradableSpecification _qualityUpgradableSpecification;
private readonly Logger _logger; private readonly Logger _logger;
@ -31,6 +34,7 @@ public MovieFileModule(IBroadcastSignalRMessage signalRBroadcaster,
IMediaFileService mediaFileService, IMediaFileService mediaFileService,
IRecycleBinProvider recycleBinProvider, IRecycleBinProvider recycleBinProvider,
IMovieService movieService, IMovieService movieService,
ICustomFormatCalculationService formatCalculator,
IUpgradableSpecification qualityUpgradableSpecification, IUpgradableSpecification qualityUpgradableSpecification,
Logger logger) Logger logger)
: base(signalRBroadcaster) : base(signalRBroadcaster)
@ -38,6 +42,7 @@ public MovieFileModule(IBroadcastSignalRMessage signalRBroadcaster,
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_recycleBinProvider = recycleBinProvider; _recycleBinProvider = recycleBinProvider;
_movieService = movieService; _movieService = movieService;
_formatCalculator = formatCalculator;
_qualityUpgradableSpecification = qualityUpgradableSpecification; _qualityUpgradableSpecification = qualityUpgradableSpecification;
_logger = logger; _logger = logger;
@ -54,8 +59,11 @@ private MovieFileResource GetMovieFile(int id)
{ {
var movieFile = _mediaFileService.GetMovie(id); var movieFile = _mediaFileService.GetMovie(id);
var movie = _movieService.GetMovie(movieFile.MovieId); 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() private List<MovieFileResource> GetMovieFiles()
@ -72,8 +80,18 @@ private List<MovieFileResource> GetMovieFiles()
{ {
int movieId = Convert.ToInt32(movieIdQuery.Value); int movieId = Convert.ToInt32(movieIdQuery.Value);
var movie = _movieService.GetMovie(movieId); 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 else
{ {
@ -95,6 +113,7 @@ private List<MovieFileResource> GetMovieFiles()
private void SetMovieFile(MovieFileResource movieFileResource) private void SetMovieFile(MovieFileResource movieFileResource)
{ {
var movieFile = _mediaFileService.GetMovie(movieFileResource.Id); var movieFile = _mediaFileService.GetMovie(movieFileResource.Id);
movieFile.IndexerFlags = movieFileResource.IndexerFlags;
movieFile.Quality = movieFileResource.Quality; movieFile.Quality = movieFileResource.Quality;
movieFile.Languages = movieFileResource.Languages; movieFile.Languages = movieFileResource.Languages;
_mediaFileService.Update(movieFile); _mediaFileService.Update(movieFile);

View File

@ -4,7 +4,9 @@
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using Radarr.Api.V3.CustomFormats;
using Radarr.Http.REST; using Radarr.Http.REST;
namespace Radarr.Api.V3.MovieFiles namespace Radarr.Api.V3.MovieFiles
@ -17,7 +19,9 @@ public class MovieFileResource : RestResource
public long Size { get; set; } public long Size { get; set; }
public DateTime DateAdded { get; set; } public DateTime DateAdded { get; set; }
public string SceneName { get; set; } public string SceneName { get; set; }
public IndexerFlags IndexerFlags { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public List<CustomFormatResource> CustomFormats { get; set; }
public MediaInfoResource MediaInfo { get; set; } public MediaInfoResource MediaInfo { get; set; }
public bool QualityCutoffNotMet { get; set; } public bool QualityCutoffNotMet { get; set; }
public List<Language> Languages { get; set; } public List<Language> Languages { get; set; }
@ -43,6 +47,7 @@ private static MovieFileResource ToResource(this MovieFile model)
Size = model.Size, Size = model.Size,
DateAdded = model.DateAdded, DateAdded = model.DateAdded,
SceneName = model.SceneName, SceneName = model.SceneName,
IndexerFlags = model.IndexerFlags,
Quality = model.Quality, Quality = model.Quality,
Languages = model.Languages, Languages = model.Languages,
MediaInfo = model.MediaInfo.ToResource(model.SceneName), MediaInfo = model.MediaInfo.ToResource(model.SceneName),
@ -68,6 +73,7 @@ public static MovieFileResource ToResource(this MovieFile model, NzbDrone.Core.M
Size = model.Size, Size = model.Size,
DateAdded = model.DateAdded, DateAdded = model.DateAdded,
SceneName = model.SceneName, SceneName = model.SceneName,
IndexerFlags = model.IndexerFlags,
Quality = model.Quality, Quality = model.Quality,
Languages = model.Languages, Languages = model.Languages,
MediaInfo = model.MediaInfo.ToResource(model.SceneName) MediaInfo = model.MediaInfo.ToResource(model.SceneName)
@ -91,10 +97,11 @@ public static MovieFileResource ToResource(this MovieFile model, NzbDrone.Core.M
Size = model.Size, Size = model.Size,
DateAdded = model.DateAdded, DateAdded = model.DateAdded,
SceneName = model.SceneName, SceneName = model.SceneName,
IndexerFlags = model.IndexerFlags,
Quality = model.Quality, Quality = model.Quality,
Languages = model.Languages, Languages = model.Languages,
MediaInfo = model.MediaInfo.ToResource(model.SceneName), 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