Preferred words

New: Ability to prefer releases based on terms in release title
This commit is contained in:
Mark McDowall 2018-10-20 13:27:51 -07:00 committed by Taloth Saldono
parent ac709c39ab
commit 853f25468c
70 changed files with 852 additions and 347 deletions

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Api.Episodes; using NzbDrone.Api.Episodes;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.SignalR; using NzbDrone.SignalR;

View File

@ -5,6 +5,7 @@ using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Exceptions; using NzbDrone.Core.Exceptions;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using Sonarr.Http; using Sonarr.Http;

View File

@ -1,6 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using Sonarr.Http.REST; using Sonarr.Http.REST;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;

View File

@ -2,6 +2,7 @@
using Sonarr.Http.REST; using Sonarr.Http.REST;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.SignalR; using NzbDrone.SignalR;
namespace NzbDrone.Api.Episodes namespace NzbDrone.Api.Episodes

View File

@ -4,6 +4,7 @@ using NzbDrone.Api.Series;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;

View File

@ -7,6 +7,7 @@ using Sonarr.Http.Extensions;
using NzbDrone.Api.Series; using NzbDrone.Api.Series;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.History; using NzbDrone.Core.History;
using Sonarr.Http; using Sonarr.Http;

View File

@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Restrictions; using NzbDrone.Core.Profiles.Releases;
using Sonarr.Http; using Sonarr.Http;
using Sonarr.Http.Mapping; using Sonarr.Http.Mapping;
@ -9,12 +9,12 @@ namespace NzbDrone.Api.Restrictions
{ {
public class RestrictionModule : SonarrRestModule<RestrictionResource> public class RestrictionModule : SonarrRestModule<RestrictionResource>
{ {
private readonly IRestrictionService _restrictionService; private readonly IReleaseProfileService _releaseProfileService;
public RestrictionModule(IRestrictionService restrictionService) public RestrictionModule(IReleaseProfileService releaseProfileService)
{ {
_restrictionService = restrictionService; _releaseProfileService = releaseProfileService;
GetResourceById = GetRestriction; GetResourceById = GetRestriction;
GetResourceAll = GetAllRestrictions; GetResourceAll = GetAllRestrictions;
@ -35,27 +35,27 @@ namespace NzbDrone.Api.Restrictions
private RestrictionResource GetRestriction(int id) private RestrictionResource GetRestriction(int id)
{ {
return _restrictionService.Get(id).ToResource(); return _releaseProfileService.Get(id).ToResource();
} }
private List<RestrictionResource> GetAllRestrictions() private List<RestrictionResource> GetAllRestrictions()
{ {
return _restrictionService.All().ToResource(); return _releaseProfileService.All().ToResource();
} }
private int CreateRestriction(RestrictionResource resource) private int CreateRestriction(RestrictionResource resource)
{ {
return _restrictionService.Add(resource.ToModel()).Id; return _releaseProfileService.Add(resource.ToModel()).Id;
} }
private void UpdateRestriction(RestrictionResource resource) private void UpdateRestriction(RestrictionResource resource)
{ {
_restrictionService.Update(resource.ToModel()); _releaseProfileService.Update(resource.ToModel());
} }
private void DeleteRestriction(int id) private void DeleteRestriction(int id)
{ {
_restrictionService.Delete(id); _releaseProfileService.Delete(id);
} }
} }
} }

View File

@ -1,14 +1,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.Profiles.Releases;
using Sonarr.Http.REST; using Sonarr.Http.REST;
using NzbDrone.Core.Restrictions;
namespace NzbDrone.Api.Restrictions namespace NzbDrone.Api.Restrictions
{ {
public class RestrictionResource : RestResource public class RestrictionResource : RestResource
{ {
public string Required { get; set; } public string Required { get; set; }
public string Preferred { get; set; }
public string Ignored { get; set; } public string Ignored { get; set; }
public HashSet<int> Tags { get; set; } public HashSet<int> Tags { get; set; }
@ -20,7 +19,7 @@ namespace NzbDrone.Api.Restrictions
public static class RestrictionResourceMapper public static class RestrictionResourceMapper
{ {
public static RestrictionResource ToResource(this Restriction model) public static RestrictionResource ToResource(this ReleaseProfile model)
{ {
if (model == null) return null; if (model == null) return null;
@ -29,28 +28,26 @@ namespace NzbDrone.Api.Restrictions
Id = model.Id, Id = model.Id,
Required = model.Required, Required = model.Required,
Preferred = model.Preferred,
Ignored = model.Ignored, Ignored = model.Ignored,
Tags = new HashSet<int>(model.Tags) Tags = new HashSet<int>(model.Tags)
}; };
} }
public static Restriction ToModel(this RestrictionResource resource) public static ReleaseProfile ToModel(this RestrictionResource resource)
{ {
if (resource == null) return null; if (resource == null) return null;
return new Restriction return new ReleaseProfile
{ {
Id = resource.Id, Id = resource.Id,
Required = resource.Required, Required = resource.Required,
Preferred = resource.Preferred,
Ignored = resource.Ignored, Ignored = resource.Ignored,
Tags = new HashSet<int>(resource.Tags) Tags = new HashSet<int>(resource.Tags)
}; };
} }
public static List<RestrictionResource> ToResource(this IEnumerable<Restriction> models) public static List<RestrictionResource> ToResource(this IEnumerable<ReleaseProfile> models)
{ {
return models.Select(ToResource).ToList(); return models.Select(ToResource).ToList();
} }

View File

@ -2,6 +2,7 @@ using System.Linq;
using NzbDrone.Api.Episodes; using NzbDrone.Api.Episodes;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using Sonarr.Http; using Sonarr.Http;

View File

@ -2,6 +2,7 @@ using System.Linq;
using NzbDrone.Api.Episodes; using NzbDrone.Api.Episodes;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using Sonarr.Http; using Sonarr.Http;

View File

@ -1,8 +1,8 @@
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Languages;
@ -13,6 +13,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[TestFixture] [TestFixture]
public class CutoffSpecificationFixture : CoreTest<UpgradableSpecification> public class CutoffSpecificationFixture : CoreTest<UpgradableSpecification>
{ {
private static readonly int NoPreferredWordScore = 0;
[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()
{ {
@ -27,7 +29,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Languages = LanguageFixture.GetDefaultLanguages(Language.English), Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English Cutoff = Language.English
}, },
new QualityModel(Quality.DVD, new Revision(version: 2)), Language.English).Should().BeTrue(); new QualityModel(Quality.DVD, new Revision(version: 2)),
Language.English,
NoPreferredWordScore).Should().BeTrue();
} }
[Test] [Test]
@ -44,7 +48,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Languages = LanguageFixture.GetDefaultLanguages(Language.English), Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English Cutoff = Language.English
}, },
new QualityModel(Quality.HDTV720p, new Revision(version: 2)), Language.English).Should().BeFalse(); new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.English,
NoPreferredWordScore).Should().BeFalse();
} }
[Test] [Test]
@ -61,7 +67,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Languages = LanguageFixture.GetDefaultLanguages(Language.English), Languages = LanguageFixture.GetDefaultLanguages(Language.English),
Cutoff = Language.English Cutoff = Language.English
}, },
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), Language.English).Should().BeFalse(); new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
Language.English,
NoPreferredWordScore).Should().BeFalse();
} }
[Test] [Test]
@ -80,7 +88,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
}, },
new QualityModel(Quality.HDTV720p, new Revision(version: 1)), new QualityModel(Quality.HDTV720p, new Revision(version: 1)),
Language.English, Language.English,
new QualityModel(Quality.HDTV720p, new Revision(version: 2))).Should().BeTrue(); NoPreferredWordScore,
new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeTrue();
} }
[Test] [Test]
@ -99,13 +109,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
}, },
new QualityModel(Quality.HDTV720p, new Revision(version: 2)), new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.English, Language.English,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeFalse();
} }
[Test] [Test]
public void should_return_true_if_quality_cutoff_is_met_and_quality_is_higher_but_language_is_not_met() public void should_return_true_if_quality_cutoff_is_met_and_quality_is_higher_but_language_is_not_met()
{ {
Profile _profile = new Profile Profile _profile = new Profile
{ {
Cutoff = Quality.HDTV720p.Id, Cutoff = Quality.HDTV720p.Id,
@ -122,13 +133,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_langProfile, _langProfile,
new QualityModel(Quality.HDTV720p, new Revision(version: 2)), new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.English, Language.English,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeTrue(); NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeTrue();
} }
[Test] [Test]
public void should_return_false_if_cutoff_is_met_and_quality_is_higher_and_language_is_met() public void should_return_false_if_cutoff_is_met_and_quality_is_higher_and_language_is_met()
{ {
Profile _profile = new Profile Profile _profile = new Profile
{ {
Cutoff = Quality.HDTV720p.Id, Cutoff = Quality.HDTV720p.Id,
@ -146,13 +158,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_langProfile, _langProfile,
new QualityModel(Quality.HDTV720p, new Revision(version: 2)), new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.Spanish, Language.Spanish,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeFalse();
} }
[Test] [Test]
public void should_return_false_if_cutoff_is_met_and_quality_is_higher_and_language_is_higher() public void should_return_false_if_cutoff_is_met_and_quality_is_higher_and_language_is_higher()
{ {
Profile _profile = new Profile Profile _profile = new Profile
{ {
Cutoff = Quality.HDTV720p.Id, Cutoff = Quality.HDTV720p.Id,
@ -170,13 +183,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_langProfile, _langProfile,
new QualityModel(Quality.HDTV720p, new Revision(version: 2)), new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.French, Language.French,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeFalse(); NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeFalse();
} }
[Test] [Test]
public void should_return_true_if_cutoff_is_not_met_and_new_quality_is_higher_and_language_is_higher() public void should_return_true_if_cutoff_is_not_met_and_new_quality_is_higher_and_language_is_higher()
{ {
Profile _profile = new Profile Profile _profile = new Profile
{ {
Cutoff = Quality.HDTV720p.Id, Cutoff = Quality.HDTV720p.Id,
@ -194,13 +208,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_langProfile, _langProfile,
new QualityModel(Quality.SDTV, new Revision(version: 2)), new QualityModel(Quality.SDTV, new Revision(version: 2)),
Language.French, Language.French,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2))).Should().BeTrue(); NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
NoPreferredWordScore).Should().BeTrue();
} }
[Test] [Test]
public void should_return_true_if_cutoff_is_not_met_and_language_is_higher() public void should_return_true_if_cutoff_is_not_met_and_language_is_higher()
{ {
Profile _profile = new Profile Profile _profile = new Profile
{ {
Cutoff = Quality.HDTV720p.Id, Cutoff = Quality.HDTV720p.Id,
@ -217,7 +232,33 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_profile, _profile,
_langProfile, _langProfile,
new QualityModel(Quality.SDTV, new Revision(version: 2)), new QualityModel(Quality.SDTV, new Revision(version: 2)),
Language.French).Should().BeTrue(); Language.French,
NoPreferredWordScore).Should().BeTrue();
}
[Test]
public void should_return_true_if_cutoffs_are_met_and_score_is_higher()
{
Profile _profile = new Profile
{
Cutoff = Quality.HDTV720p.Id,
Items = Qualities.QualityFixture.GetDefaultQualities(),
};
LanguageProfile _langProfile = new LanguageProfile
{
Cutoff = Language.Spanish,
Languages = LanguageFixture.GetDefaultLanguages()
};
Subject.CutoffNotMet(
_profile,
_langProfile,
new QualityModel(Quality.HDTV720p, new Revision(version: 2)),
Language.Spanish,
NoPreferredWordScore,
new QualityModel(Quality.Bluray1080p, new Revision(version: 2)),
10).Should().BeTrue();
} }
} }
} }

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
@ -11,6 +11,7 @@ using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using NzbDrone.Core.DecisionEngine.Specifications;
namespace NzbDrone.Core.Test.DecisionEngineTests namespace NzbDrone.Core.Test.DecisionEngineTests
{ {

View File

@ -1,9 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -26,6 +25,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private Series _otherSeries; private Series _otherSeries;
private Episode _otherEpisode; private Episode _otherEpisode;
private ReleaseInfo _releaseInfo;
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
@ -58,10 +59,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.With(e => e.EpisodeNumber = 2) .With(e => e.EpisodeNumber = 2)
.Build(); .Build();
_releaseInfo = Builder<ReleaseInfo>.CreateNew()
.Build();
_remoteEpisode = Builder<RemoteEpisode>.CreateNew() _remoteEpisode = Builder<RemoteEpisode>.CreateNew()
.With(r => r.Series = _series) .With(r => r.Series = _series)
.With(r => r.Episodes = new List<Episode> { _episode }) .With(r => r.Episodes = new List<Episode> { _episode })
.With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD) , Language = Language.Spanish}) .With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD) , Language = Language.Spanish})
.With(r => r.PreferredWordScore = 0)
.Build(); .Build();
} }
@ -95,9 +100,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_return_true_when_series_doesnt_match() public void should_return_true_when_series_doesnt_match()
{ {
var remoteEpisode = Builder<RemoteEpisode>.CreateNew() var remoteEpisode = Builder<RemoteEpisode>.CreateNew()
.With(r => r.Series = _otherSeries) .With(r => r.Series = _otherSeries)
.With(r => r.Episodes = new List<Episode> { _episode }) .With(r => r.Episodes = new List<Episode> { _episode })
.Build(); .With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }); GivenQueue(new List<RemoteEpisode> { remoteEpisode });
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
@ -117,6 +123,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.SDTV), Quality = new QualityModel(Quality.SDTV),
Language = Language.Spanish Language = Language.Spanish
}) })
.With(r => r.Release = _releaseInfo)
.Build(); .Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }); GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -137,6 +144,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.SDTV), Quality = new QualityModel(Quality.SDTV),
Language = Language.English Language = Language.English
}) })
.With(r => r.Release = _releaseInfo)
.Build(); .Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }); GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -153,12 +161,33 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
Quality = new QualityModel(Quality.DVD) Quality = new QualityModel(Quality.DVD)
}) })
.With(r => r.Release = _releaseInfo)
.Build(); .Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }); GivenQueue(new List<RemoteEpisode> { remoteEpisode });
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
} }
[Test]
public void should_return_true_when_qualities_are_the_same_and_languages_are_the_same_with_higher_preferred_word_score()
{
_remoteEpisode.PreferredWordScore = 1;
var remoteEpisode = Builder<RemoteEpisode>.CreateNew()
.With(r => r.Series = _series)
.With(r => r.Episodes = new List<Episode> { _episode })
.With(r => r.ParsedEpisodeInfo = new ParsedEpisodeInfo
{
Quality = new QualityModel(Quality.DVD),
Language = Language.Spanish,
})
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test] [Test]
public void should_return_false_when_qualities_are_the_same_and_languages_are_the_same() public void should_return_false_when_qualities_are_the_same_and_languages_are_the_same()
{ {
@ -170,6 +199,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.DVD), Quality = new QualityModel(Quality.DVD),
Language = Language.Spanish, Language = Language.Spanish,
}) })
.With(r => r.Release = _releaseInfo)
.Build(); .Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }); GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -187,6 +217,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.DVD), Quality = new QualityModel(Quality.DVD),
Language = Language.English, Language = Language.English,
}) })
.With(r => r.Release = _releaseInfo)
.Build(); .Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }); GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -206,6 +237,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.HDTV720p), Quality = new QualityModel(Quality.HDTV720p),
Language = Language.English Language = Language.English
}) })
.With(r => r.Release = _releaseInfo)
.Build(); .Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }); GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -223,6 +255,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.HDTV720p), Quality = new QualityModel(Quality.HDTV720p),
Language = Language.English Language = Language.English
}) })
.With(r => r.Release = _releaseInfo)
.Build(); .Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }); GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -240,6 +273,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.HDTV720p), Quality = new QualityModel(Quality.HDTV720p),
Language = Language.English Language = Language.English
}) })
.With(r => r.Release = _releaseInfo)
.Build(); .Build();
_remoteEpisode.Episodes.Add(_otherEpisode); _remoteEpisode.Episodes.Add(_otherEpisode);
@ -259,6 +293,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.HDTV720p), Quality = new QualityModel(Quality.HDTV720p),
Language = Language.English Language = Language.English
}) })
.With(r => r.Release = _releaseInfo)
.Build(); .Build();
_remoteEpisode.Episodes.Add(_otherEpisode); _remoteEpisode.Episodes.Add(_otherEpisode);
@ -280,6 +315,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality.HDTV720p), Quality.HDTV720p),
Language = Language.English Language = Language.English
}) })
.With(r => r.Release = _releaseInfo)
.TheFirst(1) .TheFirst(1)
.With(r => r.Episodes = new List<Episode> { _episode }) .With(r => r.Episodes = new List<Episode> { _episode })
.TheNext(1) .TheNext(1)
@ -304,6 +340,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Quality = new QualityModel(Quality.HDTV720p), Quality = new QualityModel(Quality.HDTV720p),
Language = Language.Spanish Language = Language.Spanish
}) })
.With(r => r.Release = _releaseInfo)
.Build(); .Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }); GivenQueue(new List<RemoteEpisode> { remoteEpisode });

View File

@ -1,10 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Restrictions; using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
@ -35,11 +35,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void GivenRestictions(string required, string ignored) private void GivenRestictions(string required, string ignored)
{ {
Mocker.GetMock<IRestrictionService>() Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>())) .Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Returns(new List<Restriction> .Returns(new List<ReleaseProfile>
{ {
new Restriction new ReleaseProfile()
{ {
Required = required, Required = required,
Ignored = ignored Ignored = ignored
@ -50,9 +50,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test] [Test]
public void should_be_true_when_restrictions_are_empty() public void should_be_true_when_restrictions_are_empty()
{ {
Mocker.GetMock<IRestrictionService>() Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>())) .Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Returns(new List<Restriction>()); .Returns(new List<ReleaseProfile>());
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
} }
@ -116,11 +116,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
_remoteEpisode.Release.Title = "[ www.Speed.cd ] -Whose.Line.is.it.Anyway.US.S10E24.720p.HDTV.x264-BAJSKORV"; _remoteEpisode.Release.Title = "[ www.Speed.cd ] -Whose.Line.is.it.Anyway.US.S10E24.720p.HDTV.x264-BAJSKORV";
Mocker.GetMock<IRestrictionService>() Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>())) .Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Returns(new List<Restriction> .Returns(new List<ReleaseProfile>
{ {
new Restriction { Required = "x264", Ignored = "www.Speed.cd" } new ReleaseProfile { Required = "x264", Ignored = "www.Speed.cd" }
}); });
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
@ -6,7 +6,7 @@ using FluentAssertions;
using Marr.Data; using Marr.Data;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.DecisionEngine; 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;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
@ -86,14 +86,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync
_remoteEpisode.Episodes.First().EpisodeFile = new LazyLoaded<EpisodeFile>(new EpisodeFile _remoteEpisode.Episodes.First().EpisodeFile = new LazyLoaded<EpisodeFile>(new EpisodeFile
{ {
Quality = quality, Quality = quality,
Language = language Language = language,
SceneName = "Series.Title.S01E01.720p.HDTV.x264-Sonarr"
}); });
} }
private void GivenUpgradeForExistingFile() private void GivenUpgradeForExistingFile()
{ {
Mocker.GetMock<IUpgradableSpecification>() Mocker.GetMock<IUpgradableSpecification>()
.Setup(s => s.IsUpgradable(It.IsAny<Profile>(), It.IsAny<LanguageProfile>(), It.IsAny<QualityModel>(), It.IsAny<Language>(), It.IsAny<QualityModel>(), It.IsAny<Language>())) .Setup(s => s.IsUpgradable(It.IsAny<Profile>(), It.IsAny<LanguageProfile>(), It.IsAny<QualityModel>(), It.IsAny<Language>(), It.IsAny<int>(), It.IsAny<QualityModel>(), It.IsAny<Language>(), It.IsAny<int>()))
.Returns(true); .Returns(true);
} }

View File

@ -13,6 +13,7 @@ using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;

View File

@ -12,7 +12,7 @@ using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync

View File

@ -1,10 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;

View File

@ -1,9 +1,9 @@
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Languages;
@ -13,7 +13,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
[TestFixture] [TestFixture]
public class QualityUpgradeSpecificationFixture : CoreTest<UpgradableSpecification> public class UpgradeSpecificationFixture : CoreTest<UpgradableSpecification>
{ {
public static object[] IsUpgradeTestCases = public static object[] IsUpgradeTestCases =
{ {
@ -36,11 +36,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
new object[] { Quality.WEBDL720p, 1, Language.Spanish, Quality.HDTV720p, 2, Language.French, Quality.WEBDL720p, Language.Spanish, false } new object[] { Quality.WEBDL720p, 1, Language.Spanish, Quality.HDTV720p, 2, Language.French, Quality.WEBDL720p, Language.Spanish, false }
}; };
[SetUp] private static readonly int NoPreferredWordScore = 0;
public void Setup()
{
}
private void GivenAutoDownloadPropers(bool autoDownloadPropers) private void GivenAutoDownloadPropers(bool autoDownloadPropers)
{ {
@ -66,7 +62,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Cutoff = Language.English Cutoff = Language.English
}; };
Subject.IsUpgradable(profile, langProfile, new QualityModel(current, new Revision(version: currentVersion)), Language.English, new QualityModel(newQuality, new Revision(version: newVersion)), Language.English) Subject.IsUpgradable(
profile,
langProfile,
new QualityModel(current, new Revision(version: currentVersion)),
Language.English,
NoPreferredWordScore,
new QualityModel(newQuality, new Revision(version: newVersion)),
Language.English,
NoPreferredWordScore)
.Should().Be(expected); .Should().Be(expected);
} }
@ -87,7 +91,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Cutoff = languageCutoff Cutoff = languageCutoff
}; };
Subject.IsUpgradable(profile, langProfile, new QualityModel(current, new Revision(version: currentVersion)), currentLanguage, new QualityModel(newQuality, new Revision(version: newVersion)), newLanguage) Subject.IsUpgradable(
profile,
langProfile,
new QualityModel(current, new Revision(version: currentVersion)),
currentLanguage,
NoPreferredWordScore,
new QualityModel(newQuality, new Revision(version: newVersion)),
newLanguage,
NoPreferredWordScore)
.Should().Be(expected); .Should().Be(expected);
} }
@ -108,7 +120,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
}; };
Subject.IsUpgradable(profile, langProfile, new QualityModel(Quality.DVD, new Revision(version: 2)), Language.English, new QualityModel(Quality.DVD, new Revision(version: 1)), Language.English) Subject.IsUpgradable(
profile,
langProfile,
new QualityModel(Quality.DVD, new Revision(version: 2)),
Language.English,
NoPreferredWordScore,
new QualityModel(Quality.DVD, new Revision(version: 1)),
Language.English,
NoPreferredWordScore)
.Should().BeFalse(); .Should().BeFalse();
} }
} }

View File

@ -1,10 +1,10 @@
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Housekeeping.Housekeepers; using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tags; using NzbDrone.Core.Tags;
using NzbDrone.Core.Restrictions; using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{ {
@ -27,7 +27,7 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
var tags = Builder<Tag>.CreateListOfSize(2).BuildList(); var tags = Builder<Tag>.CreateListOfSize(2).BuildList();
Db.InsertMany(tags); Db.InsertMany(tags);
var restrictions = Builder<Restriction>.CreateListOfSize(2) var restrictions = Builder<ReleaseProfile>.CreateListOfSize(2)
.All() .All()
.With(v => v.Tags.Add(tags[0].Id)) .With(v => v.Tags.Add(tags[0].Id))
.BuildList(); .BuildList();

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
@ -42,7 +42,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeFileMovingServiceTests
.Build(); .Build();
Mocker.GetMock<IBuildFileNames>() Mocker.GetMock<IBuildFileNames>()
.Setup(s => s.BuildFileName(It.IsAny<List<Episode>>(), It.IsAny<Series>(), It.IsAny<EpisodeFile>(), null)) .Setup(s => s.BuildFileName(It.IsAny<List<Episode>>(), It.IsAny<Series>(), It.IsAny<EpisodeFile>(), null, null))
.Returns("File Name"); .Returns("File Name");
Mocker.GetMock<IBuildFileNames>() Mocker.GetMock<IBuildFileNames>()

View File

@ -172,7 +172,7 @@
<Compile Include="DecisionEngineTests\ReleaseRestrictionsSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\ReleaseRestrictionsSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\PrioritizeDownloadDecisionFixture.cs" /> <Compile Include="DecisionEngineTests\PrioritizeDownloadDecisionFixture.cs" />
<Compile Include="DecisionEngineTests\QualityAllowedByProfileSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\QualityAllowedByProfileSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\QualityUpgradeSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\UpgradeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\MinimumAgeSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\MinimumAgeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RssSync\DelaySpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\RssSync\DelaySpecificationFixture.cs" />
@ -346,6 +346,7 @@
<Compile Include="ParserTests\ValidateParsedEpisodeInfoFixture.cs" /> <Compile Include="ParserTests\ValidateParsedEpisodeInfoFixture.cs" />
<Compile Include="Profiles\Delay\DelayProfileServiceFixture.cs" /> <Compile Include="Profiles\Delay\DelayProfileServiceFixture.cs" />
<Compile Include="Profiles\Qualities\QualityIndexCompareToFixture.cs" /> <Compile Include="Profiles\Qualities\QualityIndexCompareToFixture.cs" />
<Compile Include="Profiles\Releases\PreferredWordService\CalculateFixture.cs" />
<Compile Include="Qualities\QualityFinderFixture.cs" /> <Compile Include="Qualities\QualityFinderFixture.cs" />
<Compile Include="Qualities\RevisionComparableFixture.cs" /> <Compile Include="Qualities\RevisionComparableFixture.cs" />
<Compile Include="QueueTests\QueueServiceFixture.cs" /> <Compile Include="QueueTests\QueueServiceFixture.cs" />

View File

@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
{
[TestFixture]
public class CalculateFixture : CoreTest<Core.Profiles.Releases.PreferredWordService>
{
private Series _series = null;
private List<ReleaseProfile> _releaseProfiles = null;
private string _title = "Series.Title.S01E01.720p.HDTV.x264-Sonarr";
[SetUp]
public void Setup()
{
_series = Builder<Series>.CreateNew()
.With(s => s.Tags = new HashSet<int>(new[] {1, 2}))
.Build();
_releaseProfiles = new List<ReleaseProfile>();
_releaseProfiles.Add(new ReleaseProfile
{
Preferred = new List<KeyValuePair<string, int>>
{
new KeyValuePair<string, int>("x264", 5),
new KeyValuePair<string, int>("x265", -10)
}
});
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Returns(_releaseProfiles);
}
private void GivenMatchingTerms(params string[] terms)
{
Mocker.GetMock<ITermMatcher>()
.Setup(s => s.IsMatch(It.IsAny<string>(), _title))
.Returns<string, string>((term, title) => terms.Contains(term));
}
[Test]
public void should_return_0_when_there_are_no_release_profiles()
{
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Returns(new List<ReleaseProfile>());
Subject.Calculate(_series, _title).Should().Be(0);
}
[Test]
public void should_return_0_when_there_are_no_matching_preferred_words()
{
GivenMatchingTerms();
Subject.Calculate(_series, _title).Should().Be(0);
}
[Test]
public void should_calculate_positive_score()
{
GivenMatchingTerms("x264");
Subject.Calculate(_series, _title).Should().Be(5);
}
[Test]
public void should_calculate_negative_score()
{
GivenMatchingTerms("x265");
Subject.Calculate(_series, _title).Should().Be(-10);
}
[Test]
public void should_calculate_using_multiple_profiles()
{
_releaseProfiles.Add(_releaseProfiles.First());
GivenMatchingTerms("x264");
Subject.Calculate(_series, _title).Should().Be(10);
}
}
}

View File

@ -0,0 +1,15 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(127)]
public class rename_restrictions_to_release_profiles : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Rename.Table("Restrictions").To("ReleaseProfiles");
Alter.Table("ReleaseProfiles").AddColumn("IncludePreferredWhenRenaming").AsBoolean().WithDefaultValue(true);
}
}
}

View File

@ -21,7 +21,6 @@ using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Restrictions;
using NzbDrone.Core.RootFolders; using NzbDrone.Core.RootFolders;
using NzbDrone.Core.SeriesStats; using NzbDrone.Core.SeriesStats;
using NzbDrone.Core.Tags; using NzbDrone.Core.Tags;
@ -37,6 +36,7 @@ using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
{ {
@ -121,7 +121,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<RemotePathMapping>().RegisterModel("RemotePathMappings"); Mapper.Entity<RemotePathMapping>().RegisterModel("RemotePathMappings");
Mapper.Entity<Tag>().RegisterModel("Tags"); Mapper.Entity<Tag>().RegisterModel("Tags");
Mapper.Entity<Restriction>().RegisterModel("Restrictions"); Mapper.Entity<ReleaseProfile>().RegisterModel("ReleaseProfiles");
Mapper.Entity<DelayProfile>().RegisterModel("DelayProfiles"); Mapper.Entity<DelayProfile>().RegisterModel("DelayProfiles");
Mapper.Entity<User>().RegisterModel("Users"); Mapper.Entity<User>().RegisterModel("Users");
@ -149,6 +149,7 @@ namespace NzbDrone.Core.Datastore
MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter())); MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter()));
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<KeyValuePair<string, int>>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(Language), new LanguageIntConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(Language), new LanguageIntConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<string>), new EmbeddedDocumentConverter()); MapRepository.Instance.RegisterTypeConverter(typeof(List<string>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<LanguageProfileItem>), new EmbeddedDocumentConverter(new LanguageIntConverter())); MapRepository.Instance.RegisterTypeConverter(typeof(List<LanguageProfileItem>), new EmbeddedDocumentConverter(new LanguageIntConverter()));

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
@ -25,6 +25,7 @@ namespace NzbDrone.Core.DecisionEngine
{ {
CompareQuality, CompareQuality,
CompareLanguage, CompareLanguage,
ComparePreferredWordScore,
CompareProtocol, CompareProtocol,
CompareEpisodeCount, CompareEpisodeCount,
CompareEpisodeNumber, CompareEpisodeNumber,
@ -68,6 +69,11 @@ namespace NzbDrone.Core.DecisionEngine
return CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Series.LanguageProfile.Value.Languages.FindIndex(l => l.Language == remoteEpisode.ParsedEpisodeInfo.Language)); return CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Series.LanguageProfile.Value.Languages.FindIndex(l => l.Language == remoteEpisode.ParsedEpisodeInfo.Language));
} }
private int ComparePreferredWordScore(DownloadDecision x, DownloadDecision y)
{
return CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.PreferredWordScore);
}
private int CompareProtocol(DownloadDecision x, DownloadDecision y) private int CompareProtocol(DownloadDecision x, DownloadDecision y)
{ {
var result = CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => var result = CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode =>

View File

@ -5,6 +5,8 @@ using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download.Aggregation;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -21,12 +23,17 @@ namespace NzbDrone.Core.DecisionEngine
{ {
private readonly IEnumerable<IDecisionEngineSpecification> _specifications; private readonly IEnumerable<IDecisionEngineSpecification> _specifications;
private readonly IParsingService _parsingService; private readonly IParsingService _parsingService;
private readonly IRemoteEpisodeAggregationService _aggregationService;
private readonly Logger _logger; private readonly Logger _logger;
public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications, IParsingService parsingService, Logger logger) public DownloadDecisionMaker(IEnumerable<IDecisionEngineSpecification> specifications,
IParsingService parsingService,
IRemoteEpisodeAggregationService aggregationService,
Logger logger)
{ {
_specifications = specifications; _specifications = specifications;
_parsingService = parsingService; _parsingService = parsingService;
_aggregationService = aggregationService;
_logger = logger; _logger = logger;
} }
@ -89,6 +96,7 @@ namespace NzbDrone.Core.DecisionEngine
} }
else else
{ {
_aggregationService.Augment(remoteEpisode);
remoteEpisode.DownloadAllowed = remoteEpisode.Episodes.Any(); remoteEpisode.DownloadAllowed = remoteEpisode.Episodes.Any();
decision = GetDecisionForReport(remoteEpisode, searchCriteria); decision = GetDecisionForReport(remoteEpisode, searchCriteria);
} }

View File

@ -1,18 +1,21 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.DecisionEngine.Specifications namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
public class CutoffSpecification : IDecisionEngineSpecification public class CutoffSpecification : IDecisionEngineSpecification
{ {
private readonly UpgradableSpecification _upgradableSpecification; private readonly UpgradableSpecification _upgradableSpecification;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger; private readonly Logger _logger;
public CutoffSpecification(UpgradableSpecification UpgradableSpecification, Logger logger) public CutoffSpecification(UpgradableSpecification upgradableSpecification, IPreferredWordService preferredWordServiceCalculator, Logger logger)
{ {
_upgradableSpecification = UpgradableSpecification; _upgradableSpecification = upgradableSpecification;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger; _logger = logger;
} }
@ -30,13 +33,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger.Debug("File is no longer available, skipping this file."); _logger.Debug("File is no longer available, skipping this file.");
continue; continue;
} }
_logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language); _logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language);
if (!_upgradableSpecification.CutoffNotMet(profile, if (!_upgradableSpecification.CutoffNotMet(profile,
subject.Series.LanguageProfile, subject.Series.LanguageProfile,
file.Quality, file.Quality,
file.Language, file.Language,
subject.ParsedEpisodeInfo.Quality)) _preferredWordServiceCalculator.Calculate(subject.Series, file.GetSceneOrFileName()),
subject.ParsedEpisodeInfo.Quality,
subject.PreferredWordScore))
{ {
_logger.Debug("Cutoff already met, rejecting."); _logger.Debug("Cutoff already met, rejecting.");

View File

@ -1,7 +1,7 @@
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
public interface IDecisionEngineSpecification public interface IDecisionEngineSpecification
{ {

View File

@ -2,6 +2,7 @@ using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Queue; using NzbDrone.Core.Queue;
namespace NzbDrone.Core.DecisionEngine.Specifications namespace NzbDrone.Core.DecisionEngine.Specifications
@ -10,14 +11,17 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
private readonly IQueueService _queueService; private readonly IQueueService _queueService;
private readonly UpgradableSpecification _upgradableSpecification; private readonly UpgradableSpecification _upgradableSpecification;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger; private readonly Logger _logger;
public QueueSpecification(IQueueService queueService, public QueueSpecification(IQueueService queueService,
UpgradableSpecification UpgradableSpecification, UpgradableSpecification UpgradableSpecification,
Logger logger) IPreferredWordService preferredWordServiceCalculator,
Logger logger)
{ {
_queueService = queueService; _queueService = queueService;
_upgradableSpecification = UpgradableSpecification; _upgradableSpecification = UpgradableSpecification;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger; _logger = logger;
} }
@ -26,21 +30,24 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{ {
var queue = _queueService.GetQueue() var queue = _queueService.GetQueue();
.Select(q => q.RemoteEpisode).ToList();
var matchingSeries = queue.Where(q => q.Series.Id == subject.Series.Id); var matchingSeries = queue.Where(q => q.Series.Id == subject.Series.Id);
var matchingEpisode = matchingSeries.Where(q => q.Episodes.Select(e => e.Id).Intersect(subject.Episodes.Select(e => e.Id)).Any()); var matchingEpisode = matchingSeries.Where(q => q.RemoteEpisode.Episodes.Select(e => e.Id).Intersect(subject.Episodes.Select(e => e.Id)).Any());
foreach (var remoteEpisode in matchingEpisode) foreach (var queueItem in matchingEpisode)
{ {
var remoteEpisode = queueItem.RemoteEpisode;
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); _logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language);
var queuedItemPreferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Series, queueItem.Title);
if (!_upgradableSpecification.CutoffNotMet(subject.Series.Profile, if (!_upgradableSpecification.CutoffNotMet(subject.Series.Profile,
subject.Series.LanguageProfile, subject.Series.LanguageProfile,
remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Quality,
remoteEpisode.ParsedEpisodeInfo.Language, remoteEpisode.ParsedEpisodeInfo.Language,
subject.ParsedEpisodeInfo.Quality)) queuedItemPreferredWordScore,
subject.ParsedEpisodeInfo.Quality,
subject.PreferredWordScore))
{ {
return Decision.Reject("Quality for release in queue already meets cutoff: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); return Decision.Reject("Quality for release in queue already meets cutoff: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language);
} }
@ -51,8 +58,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
subject.Series.LanguageProfile, subject.Series.LanguageProfile,
remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Quality,
remoteEpisode.ParsedEpisodeInfo.Language, remoteEpisode.ParsedEpisodeInfo.Language,
queuedItemPreferredWordScore,
subject.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language)) subject.ParsedEpisodeInfo.Language,
subject.PreferredWordScore))
{ {
return Decision.Reject("Quality for release in queue is of equal or higher preference: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); return Decision.Reject("Quality for release in queue is of equal or higher preference: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language);
} }

View File

@ -5,20 +5,20 @@ using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Restrictions; using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.DecisionEngine.Specifications namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
public class ReleaseRestrictionsSpecification : IDecisionEngineSpecification public class ReleaseRestrictionsSpecification : IDecisionEngineSpecification
{ {
private readonly Logger _logger; private readonly Logger _logger;
private readonly IRestrictionService _restrictionService; private readonly IReleaseProfileService _releaseProfileService;
private readonly ITermMatcher _termMatcher; private readonly ITermMatcher _termMatcher;
public ReleaseRestrictionsSpecification(ITermMatcher termMatcher, IRestrictionService restrictionService, Logger logger) public ReleaseRestrictionsSpecification(ITermMatcher termMatcher, IReleaseProfileService releaseProfileService, Logger logger)
{ {
_logger = logger; _logger = logger;
_restrictionService = restrictionService; _releaseProfileService = releaseProfileService;
_termMatcher = termMatcher; _termMatcher = termMatcher;
} }
@ -30,7 +30,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger.Debug("Checking if release meets restrictions: {0}", subject); _logger.Debug("Checking if release meets restrictions: {0}", subject);
var title = subject.Release.Title; var title = subject.Release.Title;
var restrictions = _restrictionService.AllForTags(subject.Series.Tags); var restrictions = _releaseProfileService.AllForTags(subject.Series.Tags);
var required = restrictions.Where(r => r.Required.IsNotNullOrWhiteSpace()); var required = restrictions.Where(r => r.Required.IsNotNullOrWhiteSpace());
var ignored = restrictions.Where(r => r.Ignored.IsNotNullOrWhiteSpace()); var ignored = restrictions.Where(r => r.Ignored.IsNotNullOrWhiteSpace());

View File

@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
@ -6,6 +6,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{ {
@ -14,16 +15,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
private readonly IPendingReleaseService _pendingReleaseService; private readonly IPendingReleaseService _pendingReleaseService;
private readonly IUpgradableSpecification _upgradableSpecification; private readonly IUpgradableSpecification _upgradableSpecification;
private readonly IDelayProfileService _delayProfileService; private readonly IDelayProfileService _delayProfileService;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger; private readonly Logger _logger;
public DelaySpecification(IPendingReleaseService pendingReleaseService, public DelaySpecification(IPendingReleaseService pendingReleaseService,
IUpgradableSpecification UpgradableSpecification, IUpgradableSpecification upgradableSpecification,
IDelayProfileService delayProfileService, IDelayProfileService delayProfileService,
IPreferredWordService preferredWordServiceCalculator,
Logger logger) Logger logger)
{ {
_pendingReleaseService = pendingReleaseService; _pendingReleaseService = pendingReleaseService;
_upgradableSpecification = UpgradableSpecification; _upgradableSpecification = upgradableSpecification;
_delayProfileService = delayProfileService; _delayProfileService = delayProfileService;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger; _logger = logger;
} }
@ -50,19 +54,22 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
return Decision.Accept(); return Decision.Accept();
} }
var comparer = new QualityModelComparer(profile); var qualityComparer = new QualityModelComparer(profile);
var comparerLanguage = new LanguageComparer(languageProfile); var languageComparer = new LanguageComparer(languageProfile);
if (isPreferredProtocol) if (isPreferredProtocol)
{ {
foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value))
{ {
var upgradable = _upgradableSpecification.IsUpgradable(profile, var upgradable = _upgradableSpecification.IsUpgradable(
languageProfile, profile,
file.Quality, languageProfile,
file.Language, file.Quality,
subject.ParsedEpisodeInfo.Quality, file.Language,
subject.ParsedEpisodeInfo.Language); _preferredWordServiceCalculator.Calculate(subject.Series, file.GetSceneOrFileName()),
subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language,
subject.PreferredWordScore);
if (upgradable) if (upgradable)
{ {
@ -74,8 +81,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
// If quality meets or exceeds the best allowed quality in the profile accept it immediately // If quality meets or exceeds the best allowed quality in the profile accept it immediately
var bestQualityInProfile = profile.LastAllowedQuality(); var bestQualityInProfile = profile.LastAllowedQuality();
var isBestInProfile = comparer.Compare(subject.ParsedEpisodeInfo.Quality.Quality, bestQualityInProfile) >= 0; var isBestInProfile = qualityComparer.Compare(subject.ParsedEpisodeInfo.Quality.Quality, bestQualityInProfile) >= 0;
var isBestInProfileLanguage = comparerLanguage.Compare(subject.ParsedEpisodeInfo.Language, languageProfile.LastAllowedLanguage()) >= 0; var isBestInProfileLanguage = languageComparer.Compare(subject.ParsedEpisodeInfo.Language, languageProfile.LastAllowedLanguage()) >= 0;
if (isBestInProfile && isBestInProfileLanguage && isPreferredProtocol) if (isBestInProfile && isBestInProfileLanguage && isPreferredProtocol)
{ {

View File

@ -5,6 +5,7 @@ using NzbDrone.Core.Configuration;
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;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
{ {
@ -13,16 +14,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
private readonly IHistoryService _historyService; private readonly IHistoryService _historyService;
private readonly UpgradableSpecification _upgradableSpecification; private readonly UpgradableSpecification _upgradableSpecification;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger; private readonly Logger _logger;
public HistorySpecification(IHistoryService historyService, public HistorySpecification(IHistoryService historyService,
UpgradableSpecification upgradableSpecification, UpgradableSpecification upgradableSpecification,
IConfigService configService, IConfigService configService,
IPreferredWordService preferredWordServiceCalculator,
Logger logger) Logger logger)
{ {
_historyService = historyService; _historyService = historyService;
_upgradableSpecification = upgradableSpecification; _upgradableSpecification = upgradableSpecification;
_configService = configService; _configService = configService;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger; _logger = logger;
} }
@ -48,8 +52,29 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed) if (mostRecent != null && mostRecent.EventType == HistoryEventType.Grabbed)
{ {
var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12)); var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12));
var cutoffUnmet = _upgradableSpecification.CutoffNotMet(subject.Series.Profile, subject.Series.LanguageProfile, mostRecent.Quality, mostRecent.Language, subject.ParsedEpisodeInfo.Quality);
var upgradeable = _upgradableSpecification.IsUpgradable(subject.Series.Profile, subject.Series.LanguageProfile, mostRecent.Quality, mostRecent.Language, subject.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Language); // The series will be the same as the one in history since it's the same episode.
// Instead of fetching the series from the DB reuse the known series.
var preferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Series, mostRecent.SourceTitle);
var cutoffUnmet = _upgradableSpecification.CutoffNotMet(
subject.Series.Profile,
subject.Series.LanguageProfile,
mostRecent.Quality,
mostRecent.Language,
preferredWordScore,
subject.ParsedEpisodeInfo.Quality,
subject.PreferredWordScore);
var upgradeable = _upgradableSpecification.IsUpgradable(
subject.Series.Profile,
subject.Series.LanguageProfile,
mostRecent.Quality,
mostRecent.Language,
preferredWordScore,
subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language,
subject.PreferredWordScore);
if (!recent && cdhEnabled) if (!recent && cdhEnabled)
{ {

View File

@ -3,7 +3,7 @@ using System.Linq;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Core.DecisionEngine namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
public class SameEpisodesSpecification public class SameEpisodesSpecification
{ {

View File

@ -4,14 +4,14 @@ using NzbDrone.Core.Profiles.Languages;
using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Profiles.Qualities;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
namespace NzbDrone.Core.DecisionEngine namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
public interface IUpgradableSpecification public interface IUpgradableSpecification
{ {
bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage); bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore);
bool QualityCutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null); bool QualityCutoffNotMet(Profile profile, QualityModel currentQuality, QualityModel newQuality = null);
bool LanguageCutoffNotMet(LanguageProfile languageProfile, Language currentLanguage); bool LanguageCutoffNotMet(LanguageProfile languageProfile, Language currentLanguage);
bool CutoffNotMet(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality = null); bool CutoffNotMet(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality = null, int newScore = 0);
bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality); bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality);
} }
@ -51,19 +51,38 @@ namespace NzbDrone.Core.DecisionEngine
return true; return true;
} }
private bool IsPreferredWordUpgradable(int currentScore, int newScore)
public bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage)
{ {
// If qualities are the same then check language return newScore > currentScore;
if (newQuality != null && new QualityModelComparer(profile).Compare(newQuality, currentQuality) == 0) }
public bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore)
{
if (IsQualityUpgradable(profile, currentQuality, newQuality))
{ {
return IsLanguageUpgradable(languageProfile, currentLanguage, newLanguage); return true;
} }
// If quality is worse then always return false if (new QualityModelComparer(profile).Compare(newQuality, currentQuality) != 0)
if (!IsQualityUpgradable(profile, currentQuality, newQuality))
{ {
_logger.Debug("existing item has better quality. skipping"); _logger.Debug("Existing item has better qualitys, skipping");
return false;
}
if (IsLanguageUpgradable(languageProfile, currentLanguage, newLanguage))
{
return true;
}
if (new LanguageComparer(languageProfile).Compare(newLanguage, currentLanguage) != 0)
{
_logger.Debug("Existing item has better language, skipping");
return false;
}
if (!IsPreferredWordUpgradable(currentScore, newScore))
{
_logger.Debug("Existing item has a better preferred word score, skipping");
return false; return false;
} }
@ -94,9 +113,10 @@ namespace NzbDrone.Core.DecisionEngine
return languageCompare < 0; return languageCompare < 0;
} }
public bool CutoffNotMet(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality = null) public bool CutoffNotMet(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality = null, int newScore = 0)
{ {
// If we can upgrade the language (it is not the cutoff) then doesn't matter the quality we can always get same quality with prefered language // If we can upgrade the language (it is not the cutoff) then the quality doesn't
// matter as we can always get same quality with prefered language.
if (LanguageCutoffNotMet(languageProfile, currentLanguage)) if (LanguageCutoffNotMet(languageProfile, currentLanguage))
{ {
return true; return true;
@ -107,6 +127,11 @@ namespace NzbDrone.Core.DecisionEngine
return true; return true;
} }
if (IsPreferredWordUpgradable(currentScore, newScore))
{
return true;
}
_logger.Debug("Existing item meets cut-off. skipping."); _logger.Debug("Existing item meets cut-off. skipping.");
return false; return false;

View File

@ -1,18 +1,21 @@
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.DecisionEngine.Specifications namespace NzbDrone.Core.DecisionEngine.Specifications
{ {
public class UpgradeDiskSpecification : IDecisionEngineSpecification public class UpgradeDiskSpecification : IDecisionEngineSpecification
{ {
private readonly UpgradableSpecification _upgradableSpecification; private readonly UpgradableSpecification _upgradableSpecification;
private readonly IPreferredWordService _preferredWordServiceCalculator;
private readonly Logger _logger; private readonly Logger _logger;
public UpgradeDiskSpecification(UpgradableSpecification upgradableSpecification, Logger logger) public UpgradeDiskSpecification(UpgradableSpecification upgradableSpecification, IPreferredWordService preferredWordServiceCalculator, Logger logger)
{ {
_upgradableSpecification = upgradableSpecification; _upgradableSpecification = upgradableSpecification;
_preferredWordServiceCalculator = preferredWordServiceCalculator;
_logger = logger; _logger = logger;
} }
@ -36,8 +39,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
subject.Series.LanguageProfile, subject.Series.LanguageProfile,
file.Quality, file.Quality,
file.Language, file.Language,
_preferredWordServiceCalculator.Calculate(subject.Series, file.GetSceneOrFileName()),
subject.ParsedEpisodeInfo.Quality, subject.ParsedEpisodeInfo.Quality,
subject.ParsedEpisodeInfo.Language)) subject.ParsedEpisodeInfo.Language,
subject.PreferredWordScore))
{ {
return Decision.Reject("Quality for existing file on disk is of equal or higher preference: {0} - {1}", file.Quality, file.Language); return Decision.Reject("Quality for existing file on disk is of equal or higher preference: {0} - {1}", file.Quality, file.Language);
} }

View File

@ -0,0 +1,22 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Releases;
namespace NzbDrone.Core.Download.Aggregation.Aggregators
{
public class AggregatePreferredWordScore : IAggregateRemoteEpisode
{
private readonly IPreferredWordService _preferredWordServiceCalculator;
public AggregatePreferredWordScore(IPreferredWordService preferredWordServiceCalculator)
{
_preferredWordServiceCalculator = preferredWordServiceCalculator;
}
public RemoteEpisode Aggregate(RemoteEpisode remoteEpisode)
{
remoteEpisode.PreferredWordScore = _preferredWordServiceCalculator.Calculate(remoteEpisode.Series, remoteEpisode.Release.Title);
return remoteEpisode;
}
}
}

View File

@ -0,0 +1,9 @@
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Aggregation.Aggregators
{
public interface IAggregateRemoteEpisode
{
RemoteEpisode Aggregate(RemoteEpisode remoteEpisode);
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using NLog;
using NzbDrone.Core.Download.Aggregation.Aggregators;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.Aggregation
{
public interface IRemoteEpisodeAggregationService
{
RemoteEpisode Augment(RemoteEpisode remoteEpisode);
}
public class RemoteEpisodeAggregationService : IRemoteEpisodeAggregationService
{
private readonly IEnumerable<IAggregateRemoteEpisode> _augmenters;
private readonly Logger _logger;
public RemoteEpisodeAggregationService(IEnumerable<IAggregateRemoteEpisode> augmenters,
Logger logger)
{
_augmenters = augmenters;
_logger = logger;
}
public RemoteEpisode Augment(RemoteEpisode remoteEpisode)
{
foreach (var augmenter in _augmenters)
{
try
{
augmenter.Aggregate(remoteEpisode);
}
catch (Exception ex)
{
_logger.Warn(ex, ex.Message);
}
}
return remoteEpisode;
}
}
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Marr.Data; using Marr.Data;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
@ -19,7 +19,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{ {
var mapper = _database.GetDataMapper(); var mapper = _database.GetDataMapper();
var usedTags = new[] { "Series", "Notifications", "DelayProfiles", "Restrictions" } var usedTags = new[] { "Series", "Notifications", "DelayProfiles", "ReleaseProfiles" }
.SelectMany(v => GetUsedTags(v, mapper)) .SelectMany(v => GetUsedTags(v, mapper))
.Distinct() .Distinct()
.ToArray(); .ToArray();

View File

@ -44,6 +44,11 @@ namespace NzbDrone.Core.MediaFiles
return System.IO.Path.GetFileName(RelativePath); return System.IO.Path.GetFileName(RelativePath);
} }
if (Path.IsNotNullOrWhiteSpace())
{
return System.IO.Path.GetFileName(Path);
}
return string.Empty; return string.Empty;
} }
} }

View File

@ -1,5 +1,6 @@
using NLog; using NLog;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;

View File

@ -141,6 +141,7 @@
<Compile Include="CustomFilters\CustomFilter.cs" /> <Compile Include="CustomFilters\CustomFilter.cs" />
<Compile Include="CustomFilters\CustomFilterRepository.cs" /> <Compile Include="CustomFilters\CustomFilterRepository.cs" />
<Compile Include="CustomFilters\CustomFilterService.cs" /> <Compile Include="CustomFilters\CustomFilterService.cs" />
<Compile Include="Datastore\Migration\127_rename_release_profiles.cs" />
<Compile Include="Datastore\Migration\126_add_custom_filters.cs" /> <Compile Include="Datastore\Migration\126_add_custom_filters.cs" />
<Compile Include="Extras\Metadata\MetadataSectionType.cs" /> <Compile Include="Extras\Metadata\MetadataSectionType.cs" />
<Compile Include="Download\Aggregation\RemoteEpisodeAggregationService.cs" /> <Compile Include="Download\Aggregation\RemoteEpisodeAggregationService.cs" />
@ -340,12 +341,12 @@
<Compile Include="DecisionEngine\DownloadDecisionComparer.cs" /> <Compile Include="DecisionEngine\DownloadDecisionComparer.cs" />
<Compile Include="DecisionEngine\DownloadDecisionMaker.cs" /> <Compile Include="DecisionEngine\DownloadDecisionMaker.cs" />
<Compile Include="DecisionEngine\DownloadDecisionPriorizationService.cs" /> <Compile Include="DecisionEngine\DownloadDecisionPriorizationService.cs" />
<Compile Include="DecisionEngine\IDecisionEngineSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\IDecisionEngineSpecification.cs" />
<Compile Include="DecisionEngine\IRejectWithReason.cs" /> <Compile Include="DecisionEngine\IRejectWithReason.cs" />
<Compile Include="DecisionEngine\UpgradableSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\UpgradableSpecification.cs" />
<Compile Include="DecisionEngine\Rejection.cs" /> <Compile Include="DecisionEngine\Rejection.cs" />
<Compile Include="DecisionEngine\RejectionType.cs" /> <Compile Include="DecisionEngine\RejectionType.cs" />
<Compile Include="DecisionEngine\SameEpisodesSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\SameEpisodesSpecification.cs" />
<Compile Include="DecisionEngine\SpecificationPriority.cs" /> <Compile Include="DecisionEngine\SpecificationPriority.cs" />
<Compile Include="DecisionEngine\Specifications\AcceptableSizeSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\AcceptableSizeSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\BlacklistSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\BlacklistSpecification.cs" />
@ -1005,6 +1006,7 @@
<Compile Include="Profiles\Qualities\ProfileRepository.cs" /> <Compile Include="Profiles\Qualities\ProfileRepository.cs" />
<Compile Include="Profiles\Qualities\ProfileService.cs" /> <Compile Include="Profiles\Qualities\ProfileService.cs" />
<Compile Include="Profiles\Qualities\QualityIndex.cs" /> <Compile Include="Profiles\Qualities\QualityIndex.cs" />
<Compile Include="Profiles\Releases\PreferredWordService.cs" />
<Compile Include="ProgressMessaging\ProgressMessageContext.cs" /> <Compile Include="ProgressMessaging\ProgressMessageContext.cs" />
<Compile Include="Qualities\QualityDetectionSource.cs" /> <Compile Include="Qualities\QualityDetectionSource.cs" />
<Compile Include="Qualities\QualityFinder.cs" /> <Compile Include="Qualities\QualityFinder.cs" />
@ -1134,11 +1136,11 @@
<Compile Include="Queue\Queue.cs" /> <Compile Include="Queue\Queue.cs" />
<Compile Include="Queue\QueueService.cs" /> <Compile Include="Queue\QueueService.cs" />
<Compile Include="Queue\QueueUpdatedEvent.cs" /> <Compile Include="Queue\QueueUpdatedEvent.cs" />
<Compile Include="Restrictions\PerlRegexFactory.cs" /> <Compile Include="Profiles\Releases\PerlRegexFactory.cs" />
<Compile Include="Restrictions\Restriction.cs" /> <Compile Include="Profiles\Releases\ReleaseProfile.cs" />
<Compile Include="Restrictions\RestrictionRepository.cs" /> <Compile Include="Profiles\Releases\ReleaseProfileRepository.cs" />
<Compile Include="Restrictions\RestrictionService.cs" /> <Compile Include="Profiles\Releases\ReleaseProfileService.cs" />
<Compile Include="Restrictions\TermMatcher.cs" /> <Compile Include="Profiles\Releases\TermMatcher.cs" />
<Compile Include="Rest\JsonNetSerializer.cs" /> <Compile Include="Rest\JsonNetSerializer.cs" />
<Compile Include="Rest\RestClientFactory.cs" /> <Compile Include="Rest\RestClientFactory.cs" />
<Compile Include="Rest\RestException.cs" /> <Compile Include="Rest\RestException.cs" />

View File

@ -10,6 +10,7 @@ using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
@ -17,7 +18,7 @@ namespace NzbDrone.Core.Organizer
{ {
public interface IBuildFileNames public interface IBuildFileNames
{ {
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null); string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null, List<string> preferredWords = null);
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
string BuildSeasonPath(Series series, int seasonNumber); string BuildSeasonPath(Series series, int seasonNumber);
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
@ -30,6 +31,7 @@ namespace NzbDrone.Core.Organizer
{ {
private readonly INamingConfigService _namingConfigService; private readonly INamingConfigService _namingConfigService;
private readonly IQualityDefinitionService _qualityDefinitionService; private readonly IQualityDefinitionService _qualityDefinitionService;
private readonly IPreferredWordService _preferredWordService;
private readonly ICached<EpisodeFormat[]> _episodeFormatCache; private readonly ICached<EpisodeFormat[]> _episodeFormatCache;
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache; private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
private readonly ICached<bool> _requiresEpisodeTitleCache; private readonly ICached<bool> _requiresEpisodeTitleCache;
@ -76,17 +78,19 @@ namespace NzbDrone.Core.Organizer
public FileNameBuilder(INamingConfigService namingConfigService, public FileNameBuilder(INamingConfigService namingConfigService,
IQualityDefinitionService qualityDefinitionService, IQualityDefinitionService qualityDefinitionService,
ICacheManager cacheManager, ICacheManager cacheManager,
IPreferredWordService preferredWordService,
Logger logger) Logger logger)
{ {
_namingConfigService = namingConfigService; _namingConfigService = namingConfigService;
_qualityDefinitionService = qualityDefinitionService; _qualityDefinitionService = qualityDefinitionService;
_preferredWordService = preferredWordService;
_episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat"); _episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat"); _absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
_requiresEpisodeTitleCache = cacheManager.GetCache<bool>(GetType(), "requiresEpisodeTitle"); _requiresEpisodeTitleCache = cacheManager.GetCache<bool>(GetType(), "requiresEpisodeTitle");
_logger = logger; _logger = logger;
} }
public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null) public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null, List<string> preferredWords = null)
{ {
if (namingConfig == null) if (namingConfig == null)
{ {
@ -137,6 +141,7 @@ namespace NzbDrone.Core.Organizer
AddEpisodeFileTokens(tokenHandlers, episodeFile); AddEpisodeFileTokens(tokenHandlers, episodeFile);
AddQualityTokens(tokenHandlers, series, episodeFile); AddQualityTokens(tokenHandlers, series, episodeFile);
AddMediaInfoTokens(tokenHandlers, episodeFile); AddMediaInfoTokens(tokenHandlers, episodeFile);
AddPreferredWords(tokenHandlers, series, episodeFile, preferredWords);
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
@ -564,6 +569,16 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{TvMazeId}"] = m => series.TvMazeId.ToString(); tokenHandlers["{TvMazeId}"] = m => series.TvMazeId.ToString();
} }
private void AddPreferredWords(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series, EpisodeFile episodeFile, List<string> preferredWords = null)
{
if (preferredWords == null)
{
preferredWords = _preferredWordService.GetMatchingPreferredWords(series, episodeFile.GetSceneOrFileName(), true);
}
tokenHandlers["{Preferred Words}"] = m => string.Join(" ", preferredWords);
}
private string GetLanguagesToken(string mediaInfoLanguages) private string GetLanguagesToken(string mediaInfoLanguages)
{ {
List<string> tokens = new List<string>(); List<string> tokens = new List<string>();

View File

@ -33,6 +33,7 @@ namespace NzbDrone.Core.Organizer
private static EpisodeFile _dailyEpisodeFile; private static EpisodeFile _dailyEpisodeFile;
private static EpisodeFile _animeEpisodeFile; private static EpisodeFile _animeEpisodeFile;
private static EpisodeFile _animeMultiEpisodeFile; private static EpisodeFile _animeMultiEpisodeFile;
private static List<string> _preferredWords;
public FileNameSampleService(IBuildFileNames buildFileNames) public FileNameSampleService(IBuildFileNames buildFileNames)
{ {
@ -162,6 +163,11 @@ namespace NzbDrone.Core.Organizer
ReleaseGroup = "RlsGrp", ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfoAnime MediaInfo = mediaInfoAnime
}; };
_preferredWords = new List<string>
{
"iNTERNAL"
};
} }
public SampleResult GetStandardSample(NamingConfig nameSpec) public SampleResult GetStandardSample(NamingConfig nameSpec)
@ -243,7 +249,7 @@ namespace NzbDrone.Core.Organizer
{ {
try try
{ {
return _buildFileNames.BuildFileName(episodes, series, episodeFile, nameSpec); return _buildFileNames.BuildFileName(episodes, series, episodeFile, nameSpec, _preferredWords);
} }
catch (NamingFormatException) catch (NamingFormatException)
{ {

View File

@ -14,6 +14,7 @@ namespace NzbDrone.Core.Parser.Model
public List<Episode> Episodes { get; set; } public List<Episode> Episodes { get; set; }
public bool DownloadAllowed { get; set; } public bool DownloadAllowed { get; set; }
public TorrentSeedConfiguration SeedConfiguration { get; set; } public TorrentSeedConfiguration SeedConfiguration { get; set; }
public int PreferredWordScore { get; set; }
public bool IsRecentEpisode() public bool IsRecentEpisode()
{ {

View File

@ -1,11 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Restrictions namespace NzbDrone.Core.Profiles.Releases
{ {
public static class PerlRegexFactory public static class PerlRegexFactory
{ {

View File

@ -0,0 +1,76 @@
using NLog;
using NzbDrone.Core.Tv;
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Core.Profiles.Releases
{
public interface IPreferredWordService
{
int Calculate(Series series, string title);
List<string> GetMatchingPreferredWords(Series series, string title, bool isRenaming);
}
public class PreferredWordService : IPreferredWordService
{
private readonly IReleaseProfileService _releaseProfileService;
private readonly ITermMatcher _termMatcher;
private readonly Logger _logger;
public PreferredWordService(IReleaseProfileService releaseProfileService, ITermMatcher termMatcher, Logger logger)
{
_releaseProfileService = releaseProfileService;
_termMatcher = termMatcher;
_logger = logger;
}
public int Calculate(Series series, string title)
{
_logger.Trace("Calculating preferred word score for '{0}'", title);
var matchingPairs = GetMatchingPairs(series, title, false);
var score = matchingPairs.Sum(p => p.Value);
_logger.Trace("Calculated preferred word score for '{0}': {1}", title, score);
return score;
}
public List<string> GetMatchingPreferredWords(Series series, string title, bool isRenaming)
{
var matchingPairs = GetMatchingPairs(series, title, isRenaming);
return matchingPairs.OrderByDescending(p => p.Value)
.Select(p => p.Key)
.ToList();
}
private List<KeyValuePair<string, int>> GetMatchingPairs(Series series, string title, bool isRenaming)
{
var releaseProfiles = _releaseProfileService.AllForTags(series.Tags);
var result = new List<KeyValuePair<string, int>>();
_logger.Trace("Calculating preferred word score for '{0}'", title);
foreach (var releaseProfile in releaseProfiles)
{
if (isRenaming && !releaseProfile.IncludePreferredWhenRenaming)
{
continue;
}
foreach (var preferredPair in releaseProfile.Preferred)
{
var term = preferredPair.Key;
if (_termMatcher.IsMatch(term, title))
{
result.Add(preferredPair);
}
}
}
return result;
}
}
}

View File

@ -0,0 +1,21 @@
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Profiles.Releases
{
public class ReleaseProfile : ModelBase
{
public string Required { get; set; }
public string Ignored { get; set; }
public List<KeyValuePair<string, int>> Preferred { get; set; }
public bool IncludePreferredWhenRenaming { get; set; }
public HashSet<int> Tags { get; set; }
public ReleaseProfile()
{
Preferred = new List<KeyValuePair<string, int>>();
IncludePreferredWhenRenaming = true;
Tags = new HashSet<int>();
}
}
}

View File

@ -0,0 +1,17 @@
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Profiles.Releases
{
public interface IRestrictionRepository : IBasicRepository<ReleaseProfile>
{
}
public class ReleaseProfileRepository : BasicRepository<ReleaseProfile>, IRestrictionRepository
{
public ReleaseProfileRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
}
}

View File

@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Profiles.Releases
{
public interface IReleaseProfileService
{
List<ReleaseProfile> All();
List<ReleaseProfile> AllForTag(int tagId);
List<ReleaseProfile> AllForTags(HashSet<int> tagIds);
ReleaseProfile Get(int id);
void Delete(int id);
ReleaseProfile Add(ReleaseProfile restriction);
ReleaseProfile Update(ReleaseProfile restriction);
}
public class ReleaseProfileService : IReleaseProfileService
{
private readonly IRestrictionRepository _repo;
private readonly Logger _logger;
public ReleaseProfileService(IRestrictionRepository repo, Logger logger)
{
_repo = repo;
_logger = logger;
}
public List<ReleaseProfile> All()
{
return _repo.All().ToList();
}
public List<ReleaseProfile> AllForTag(int tagId)
{
return _repo.All().Where(r => r.Tags.Contains(tagId)).ToList();
}
public List<ReleaseProfile> AllForTags(HashSet<int> tagIds)
{
return _repo.All().Where(r => r.Tags.Intersect(tagIds).Any() || r.Tags.Empty()).ToList();
}
public ReleaseProfile Get(int id)
{
return _repo.Get(id);
}
public void Delete(int id)
{
_repo.Delete(id);
}
public ReleaseProfile Add(ReleaseProfile restriction)
{
return _repo.Insert(restriction);
}
public ReleaseProfile Update(ReleaseProfile restriction)
{
return _repo.Update(restriction);
}
}
}

View File

@ -1,11 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
namespace NzbDrone.Core.Restrictions namespace NzbDrone.Core.Profiles.Releases
{ {
public interface ITermMatcher public interface ITermMatcher
{ {

View File

@ -1,18 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Restrictions
{
public class Restriction : ModelBase
{
public string Required { get; set; }
public string Preferred { get; set; }
public string Ignored { get; set; }
public HashSet<int> Tags { get; set; }
public Restriction()
{
Tags = new HashSet<int>();
}
}
}

View File

@ -1,17 +0,0 @@
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Restrictions
{
public interface IRestrictionRepository : IBasicRepository<Restriction>
{
}
public class RestrictionRepository : BasicRepository<Restriction>, IRestrictionRepository
{
public RestrictionRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
}
}

View File

@ -1,65 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Restrictions
{
public interface IRestrictionService
{
List<Restriction> All();
List<Restriction> AllForTag(int tagId);
List<Restriction> AllForTags(HashSet<int> tagIds);
Restriction Get(int id);
void Delete(int id);
Restriction Add(Restriction restriction);
Restriction Update(Restriction restriction);
}
public class RestrictionService : IRestrictionService
{
private readonly IRestrictionRepository _repo;
private readonly Logger _logger;
public RestrictionService(IRestrictionRepository repo, Logger logger)
{
_repo = repo;
_logger = logger;
}
public List<Restriction> All()
{
return _repo.All().ToList();
}
public List<Restriction> AllForTag(int tagId)
{
return _repo.All().Where(r => r.Tags.Contains(tagId)).ToList();
}
public List<Restriction> AllForTags(HashSet<int> tagIds)
{
return _repo.All().Where(r => r.Tags.Intersect(tagIds).Any() || r.Tags.Empty()).ToList();
}
public Restriction Get(int id)
{
return _repo.Get(id);
}
public void Delete(int id)
{
_repo.Delete(id);
}
public Restriction Add(Restriction restriction)
{
return _repo.Insert(restriction);
}
public Restriction Update(Restriction restriction)
{
return _repo.Update(restriction);
}
}
}

View File

@ -3,7 +3,7 @@ using System.Linq;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Notifications; using NzbDrone.Core.Notifications;
using NzbDrone.Core.Profiles.Delay; using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Restrictions; using NzbDrone.Core.Profiles.Releases;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Tags namespace NzbDrone.Core.Tags
@ -27,21 +27,21 @@ namespace NzbDrone.Core.Tags
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IDelayProfileService _delayProfileService; private readonly IDelayProfileService _delayProfileService;
private readonly INotificationFactory _notificationFactory; private readonly INotificationFactory _notificationFactory;
private readonly IRestrictionService _restrictionService; private readonly IReleaseProfileService _releaseProfileService;
private readonly ISeriesService _seriesService; private readonly ISeriesService _seriesService;
public TagService(ITagRepository repo, public TagService(ITagRepository repo,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IDelayProfileService delayProfileService, IDelayProfileService delayProfileService,
INotificationFactory notificationFactory, INotificationFactory notificationFactory,
IRestrictionService restrictionService, IReleaseProfileService releaseProfileService,
ISeriesService seriesService) ISeriesService seriesService)
{ {
_repo = repo; _repo = repo;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_delayProfileService = delayProfileService; _delayProfileService = delayProfileService;
_notificationFactory = notificationFactory; _notificationFactory = notificationFactory;
_restrictionService = restrictionService; _releaseProfileService = releaseProfileService;
_seriesService = seriesService; _seriesService = seriesService;
} }
@ -72,7 +72,7 @@ namespace NzbDrone.Core.Tags
var tag = GetTag(tagId); var tag = GetTag(tagId);
var delayProfiles = _delayProfileService.AllForTag(tagId); var delayProfiles = _delayProfileService.AllForTag(tagId);
var notifications = _notificationFactory.AllForTag(tagId); var notifications = _notificationFactory.AllForTag(tagId);
var restrictions = _restrictionService.AllForTag(tagId); var restrictions = _releaseProfileService.AllForTag(tagId);
var series = _seriesService.AllForTag(tagId); var series = _seriesService.AllForTag(tagId);
return new TagDetails return new TagDetails
@ -91,7 +91,7 @@ namespace NzbDrone.Core.Tags
var tags = All(); var tags = All();
var delayProfiles = _delayProfileService.All(); var delayProfiles = _delayProfileService.All();
var notifications = _notificationFactory.All(); var notifications = _notificationFactory.All();
var restrictions = _restrictionService.All(); var restrictions = _releaseProfileService.All();
var series = _seriesService.GetAllSeries(); var series = _seriesService.GetAllSeries();
var details = new List<TagDetails>(); var details = new List<TagDetails>();

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using Sonarr.Api.V3.Episodes; using Sonarr.Api.V3.Episodes;

View File

@ -4,6 +4,7 @@ using System.Linq;
using Nancy; using Nancy;
using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Exceptions; using NzbDrone.Core.Exceptions;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;

View File

@ -1,6 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Nancy; using Nancy;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using Sonarr.Http.Extensions; using Sonarr.Http.Extensions;

View File

@ -2,6 +2,7 @@ using System.Collections.Generic;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;

View File

@ -4,6 +4,7 @@ using System.Linq;
using Nancy; using Nancy;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.History; using NzbDrone.Core.History;
using Sonarr.Api.V3.Episodes; using Sonarr.Api.V3.Episodes;

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using Sonarr.Http; using Sonarr.Http;
@ -28,9 +28,15 @@ namespace Sonarr.Api.V3.Indexers
if (decision.RemoteEpisode.Series != null) if (decision.RemoteEpisode.Series != null)
{ {
release.QualityWeight = decision.RemoteEpisode.Series release.QualityWeight = decision.RemoteEpisode
.Profile.Value .Series
.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 100; .Profile.Value
.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 100;
release.LanguageWeight = decision.RemoteEpisode
.Series
.LanguageProfile.Value
.Languages.FindIndex(v => v.Language == release.Language) * 100;
} }
release.QualityWeight += release.Quality.Revision.Real * 10; release.QualityWeight += release.Quality.Revision.Real * 10;

View File

@ -29,6 +29,7 @@ namespace Sonarr.Api.V3.Indexers
public bool SceneSource { get; set; } public bool SceneSource { get; set; }
public int SeasonNumber { get; set; } public int SeasonNumber { get; set; }
public Language Language { get; set; } public Language Language { get; set; }
public int LanguageWeight { get; set; }
public string AirDate { get; set; } public string AirDate { get; set; }
public string SeriesTitle { get; set; } public string SeriesTitle { get; set; }
public int[] EpisodeNumbers { get; set; } public int[] EpisodeNumbers { get; set; }
@ -45,6 +46,7 @@ namespace Sonarr.Api.V3.Indexers
public string InfoUrl { get; set; } public string InfoUrl { get; set; }
public bool DownloadAllowed { get; set; } public bool DownloadAllowed { get; set; }
public int ReleaseWeight { get; set; } public int ReleaseWeight { get; set; }
public int PreferredWordScore { get; set; }
public string MagnetUrl { get; set; } public string MagnetUrl { get; set; }
public string InfoHash { get; set; } public string InfoHash { get; set; }
@ -104,7 +106,7 @@ namespace Sonarr.Api.V3.Indexers
InfoUrl = releaseInfo.InfoUrl, InfoUrl = releaseInfo.InfoUrl,
DownloadAllowed = remoteEpisode.DownloadAllowed, DownloadAllowed = remoteEpisode.DownloadAllowed,
//ReleaseWeight //ReleaseWeight
PreferredWordScore = remoteEpisode.PreferredWordScore,
MagnetUrl = torrentInfo.MagnetUrl, MagnetUrl = torrentInfo.MagnetUrl,
InfoHash = torrentInfo.InfoHash, InfoHash = torrentInfo.InfoHash,

View File

@ -0,0 +1,60 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Profiles.Releases;
using Sonarr.Http;
namespace Sonarr.Api.V3.Profiles.Release
{
public class ReleaseProfileModule : SonarrRestModule<ReleaseProfileResource>
{
private readonly IReleaseProfileService _releaseProfileService;
public ReleaseProfileModule(IReleaseProfileService releaseProfileService)
{
_releaseProfileService = releaseProfileService;
GetResourceById = Get;
GetResourceAll = GetAll;
CreateResource = Create;
UpdateResource = Update;
DeleteResource = Delete;
SharedValidator.Custom(restriction =>
{
if (restriction.Ignored.IsNullOrWhiteSpace() && restriction.Required.IsNullOrWhiteSpace() && restriction.Preferred.Empty())
{
return new ValidationFailure("", "'Must contain', 'Must not contain' or 'Preferred' is required");
}
return null;
});
}
private ReleaseProfileResource Get(int id)
{
return _releaseProfileService.Get(id).ToResource();
}
private List<ReleaseProfileResource> GetAll()
{
return _releaseProfileService.All().ToResource();
}
private int Create(ReleaseProfileResource resource)
{
return _releaseProfileService.Add(resource.ToModel()).Id;
}
private void Update(ReleaseProfileResource resource)
{
_releaseProfileService.Update(resource.ToModel());
}
private void Delete(int id)
{
_releaseProfileService.Delete(id);
}
}
}

View File

@ -1,18 +1,19 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.Restrictions; using NzbDrone.Core.Profiles.Releases;
using Sonarr.Http.REST; using Sonarr.Http.REST;
namespace Sonarr.Api.V3.Restrictions namespace Sonarr.Api.V3.Profiles.Release
{ {
public class RestrictionResource : RestResource public class ReleaseProfileResource : RestResource
{ {
public string Required { get; set; } public string Required { get; set; }
public string Preferred { get; set; }
public string Ignored { get; set; } public string Ignored { get; set; }
public List<KeyValuePair<string, int>> Preferred { get; set; }
public bool IncludePreferredWhenRenaming { get; set; }
public HashSet<int> Tags { get; set; } public HashSet<int> Tags { get; set; }
public RestrictionResource() public ReleaseProfileResource()
{ {
Tags = new HashSet<int>(); Tags = new HashSet<int>();
} }
@ -20,37 +21,39 @@ namespace Sonarr.Api.V3.Restrictions
public static class RestrictionResourceMapper public static class RestrictionResourceMapper
{ {
public static RestrictionResource ToResource(this Restriction model) public static ReleaseProfileResource ToResource(this ReleaseProfile model)
{ {
if (model == null) return null; if (model == null) return null;
return new RestrictionResource return new ReleaseProfileResource
{ {
Id = model.Id, Id = model.Id,
Required = model.Required, Required = model.Required,
Preferred = model.Preferred,
Ignored = model.Ignored, Ignored = model.Ignored,
Preferred = model.Preferred,
IncludePreferredWhenRenaming = model.IncludePreferredWhenRenaming,
Tags = new HashSet<int>(model.Tags) Tags = new HashSet<int>(model.Tags)
}; };
} }
public static Restriction ToModel(this RestrictionResource resource) public static ReleaseProfile ToModel(this ReleaseProfileResource resource)
{ {
if (resource == null) return null; if (resource == null) return null;
return new Restriction return new ReleaseProfile
{ {
Id = resource.Id, Id = resource.Id,
Required = resource.Required, Required = resource.Required,
Preferred = resource.Preferred,
Ignored = resource.Ignored, Ignored = resource.Ignored,
Preferred = resource.Preferred,
IncludePreferredWhenRenaming = resource.IncludePreferredWhenRenaming,
Tags = new HashSet<int>(resource.Tags) Tags = new HashSet<int>(resource.Tags)
}; };
} }
public static List<RestrictionResource> ToResource(this IEnumerable<Restriction> models) public static List<ReleaseProfileResource> ToResource(this IEnumerable<ReleaseProfile> models)
{ {
return models.Select(ToResource).ToList(); return models.Select(ToResource).ToList();
} }

View File

@ -1,60 +0,0 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Restrictions;
using Sonarr.Http;
namespace Sonarr.Api.V3.Restrictions
{
public class RestrictionModule : SonarrRestModule<RestrictionResource>
{
private readonly IRestrictionService _restrictionService;
public RestrictionModule(IRestrictionService restrictionService)
{
_restrictionService = restrictionService;
GetResourceById = Get;
GetResourceAll = GetAll;
CreateResource = Create;
UpdateResource = Update;
DeleteResource = Delete;
SharedValidator.Custom(restriction =>
{
if (restriction.Ignored.IsNullOrWhiteSpace() && restriction.Required.IsNullOrWhiteSpace())
{
return new ValidationFailure("", "Either 'Must contain' or 'Must not contain' is required");
}
return null;
});
}
private RestrictionResource Get(int id)
{
return _restrictionService.Get(id).ToResource();
}
private List<RestrictionResource> GetAll()
{
return _restrictionService.All().ToResource();
}
private int Create(RestrictionResource resource)
{
return _restrictionService.Add(resource.ToModel()).Id;
}
private void Update(RestrictionResource resource)
{
_restrictionService.Update(resource.ToModel());
}
private void Delete(int id)
{
_restrictionService.Delete(id);
}
}
}

View File

@ -175,8 +175,8 @@
<Compile Include="Qualities\QualityDefinitionResource.cs" /> <Compile Include="Qualities\QualityDefinitionResource.cs" />
<Compile Include="Queue\QueueModule.cs" /> <Compile Include="Queue\QueueModule.cs" />
<Compile Include="Queue\QueueResource.cs" /> <Compile Include="Queue\QueueResource.cs" />
<Compile Include="Restrictions\RestrictionModule.cs" /> <Compile Include="Profiles\Release\ReleaseProfileModule.cs" />
<Compile Include="Restrictions\RestrictionResource.cs" /> <Compile Include="Profiles\Release\ReleaseProfileResource.cs" />
<Compile Include="RootFolders\RootFolderModule.cs" /> <Compile Include="RootFolders\RootFolderModule.cs" />
<Compile Include="RootFolders\RootFolderResource.cs" /> <Compile Include="RootFolders\RootFolderResource.cs" />
<Compile Include="SeasonPass\SeasonPassResource.cs" /> <Compile Include="SeasonPass\SeasonPassResource.cs" />

View File

@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using Sonarr.Api.V3.Episodes; using Sonarr.Api.V3.Episodes;

View File

@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using Sonarr.Api.V3.Episodes; using Sonarr.Api.V3.Episodes;