Preferred words

New: Ability to prefer releases based on terms in release title
pull/2789/head
Mark McDowall 6 years ago committed by Taloth Saldono
parent ac709c39ab
commit 853f25468c

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

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

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

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

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

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

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

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

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

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

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

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

@ -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();
} }
@ -97,6 +102,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
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 })
.With(r => r.Release = _releaseInfo)
.Build(); .Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }); GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -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,6 +161,27 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
Quality = new QualityModel(Quality.DVD) Quality = new QualityModel(Quality.DVD)
}) })
.With(r => r.Release = _releaseInfo)
.Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode });
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(); .Build();
GivenQueue(new List<RemoteEpisode> { remoteEpisode }); GivenQueue(new List<RemoteEpisode> { remoteEpisode });
@ -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 });

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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,
IPreferredWordService preferredWordServiceCalculator,
Logger logger) 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);
} }

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

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

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

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

@ -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)
{
return newScore > currentScore;
}
public bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage) public bool IsUpgradable(Profile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore)
{ {
// If qualities are the same then check language if (IsQualityUpgradable(profile, currentQuality, newQuality))
if (newQuality != null && new QualityModelComparer(profile).Compare(newQuality, currentQuality) == 0)
{ {
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;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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
.Series
.Profile.Value .Profile.Value
.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 100; .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;

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

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

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

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

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

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

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

Loading…
Cancel
Save