New: Added option to filter Release Profile to a specific indexer

Signed-off-by: Robin Dadswell <robin@dadswell.email>
This commit is contained in:
Jacob
2019-06-05 20:54:59 -05:00
committed by Qstick
parent 739ebf25c0
commit ea4044f237
30 changed files with 318 additions and 76 deletions

View File

@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void GivenRestictions(string required, string ignored)
{
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Setup(s => s.EnabledForTags(It.IsAny<HashSet<int>>(), It.IsAny<int>()))
.Returns(new List<ReleaseProfile>
{
new ReleaseProfile()
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_be_true_when_restrictions_are_empty()
{
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Setup(s => s.EnabledForTags(It.IsAny<HashSet<int>>(), It.IsAny<int>()))
.Returns(new List<ReleaseProfile>());
Subject.IsSatisfiedBy(_remoteBook, null).Accepted.Should().BeTrue();
@@ -117,7 +117,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_remoteBook.Release.Title = "[ www.Speed.cd ] - Katy Perry - Witness (2017) MP3 [320 kbps] ";
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Setup(s => s.EnabledForTags(It.IsAny<HashSet<int>>(), It.IsAny<int>()))
.Returns(new List<ReleaseProfile>
{
new ReleaseProfile { Required = "320", Ignored = "www.Speed.cd" }

View File

@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
});
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Setup(s => s.EnabledForTags(It.IsAny<HashSet<int>>(), It.IsAny<int>()))
.Returns(_releaseProfiles);
}
@@ -51,10 +51,10 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
public void should_return_0_when_there_are_no_release_profiles()
{
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Setup(s => s.EnabledForTags(It.IsAny<HashSet<int>>(), It.IsAny<int>()))
.Returns(new List<ReleaseProfile>());
Subject.Calculate(_artist, _title).Should().Be(0);
Subject.Calculate(_artist, _title, 0).Should().Be(0);
}
[Test]
@@ -62,7 +62,7 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
{
GivenMatchingTerms();
Subject.Calculate(_artist, _title).Should().Be(0);
Subject.Calculate(_artist, _title, 0).Should().Be(0);
}
[Test]
@@ -70,7 +70,7 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
{
GivenMatchingTerms("24bit");
Subject.Calculate(_artist, _title).Should().Be(5);
Subject.Calculate(_artist, _title, 0).Should().Be(5);
}
[Test]
@@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
{
GivenMatchingTerms("16bit");
Subject.Calculate(_artist, _title).Should().Be(-10);
Subject.Calculate(_artist, _title, 0).Should().Be(-10);
}
[Test]
@@ -88,7 +88,7 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
GivenMatchingTerms("24bit");
Subject.Calculate(_artist, _title).Should().Be(10);
Subject.Calculate(_artist, _title, 0).Should().Be(10);
}
}
}

View File

@@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
private void GivenReleaseProfile()
{
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Setup(s => s.EnabledForTags(It.IsAny<HashSet<int>>(), It.IsAny<int>()))
.Returns(_releaseProfiles);
}
@@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.Profiles.Releases.PreferredWordService
public void should_return_empty_list_when_there_are_no_release_profiles()
{
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.AllForTags(It.IsAny<HashSet<int>>()))
.Setup(s => s.EnabledForTags(It.IsAny<HashSet<int>>(), It.IsAny<int>()))
.Returns(new List<ReleaseProfile>());
Subject.GetMatchingPreferredWords(_artist, _title).Should().BeEmpty();

View File

@@ -17,6 +17,7 @@ namespace NzbDrone.Core.Datastore
{
IEnumerable<TModel> All();
int Count();
TModel Find(int id);
TModel Get(int id);
TModel Insert(TModel model);
TModel Update(TModel model);
@@ -88,10 +89,17 @@ namespace NzbDrone.Core.Datastore
return Query(Builder());
}
public TModel Get(int id)
public TModel Find(int id)
{
var model = Query(x => x.Id == id).FirstOrDefault();
return model;
}
public TModel Get(int id)
{
var model = Find(id);
if (model == null)
{
throw new ModelNotFoundException(typeof(TModel), id);

View File

@@ -0,0 +1,15 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(005)]
public class add_indexer_and_enabled_to_release_profiles : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("ReleaseProfiles").AddColumn("Enabled").AsBoolean().WithDefaultValue(true);
Alter.Table("ReleaseProfiles").AddColumn("IndexerId").AsInt32().WithDefaultValue(0);
}
}
}

View File

@@ -40,7 +40,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
currentQualities,
_preferredWordServiceCalculator.Calculate(subject.Author, file.GetSceneOrFileName()),
_preferredWordServiceCalculator.Calculate(subject.Author, file.GetSceneOrFileName(), subject.Release.IndexerId),
subject.ParsedBookInfo.Quality,
subject.PreferredWordScore))
{

View File

@@ -54,7 +54,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteBook.ParsedBookInfo.Quality);
var queuedItemPreferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Author, queueItem.Title);
var queuedItemPreferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Author, queueItem.Title, subject.Release?.IndexerId ?? 0);
if (!_upgradableSpecification.CutoffNotMet(qualityProfile,
new List<QualityModel> { remoteBook.ParsedBookInfo.Quality },

View File

@@ -30,10 +30,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
_logger.Debug("Checking if release meets restrictions: {0}", subject);
var title = subject.Release.Title;
var restrictions = _releaseProfileService.AllForTags(subject.Author.Tags);
var releaseProfiles = _releaseProfileService.EnabledForTags(subject.Author.Tags, subject.Release.IndexerId);
var required = restrictions.Where(r => r.Required.IsNotNullOrWhiteSpace());
var ignored = restrictions.Where(r => r.Ignored.IsNotNullOrWhiteSpace());
var required = releaseProfiles.Where(r => r.Required.IsNotNullOrWhiteSpace());
var ignored = releaseProfiles.Where(r => r.Ignored.IsNotNullOrWhiteSpace());
foreach (var r in required)
{

View File

@@ -60,9 +60,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync
continue;
}
// The author will be the same as the one in history since it's the same book.
// Instead of fetching the author from the DB reuse the known author.
var preferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Author, mostRecent.SourceTitle);
// The artist will be the same as the one in history since it's the same album.
// Instead of fetching the artist from the DB reuse the known artist.
var preferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Author, mostRecent.SourceTitle, subject.Release?.IndexerId ?? 0);
var cutoffUnmet = _upgradableSpecification.CutoffNotMet(
subject.Author.QualityProfile,

View File

@@ -36,15 +36,14 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
continue;
}
_logger.Debug("Comparing file quality and language with report. Existing file is {0}", file.Quality);
if (!_upgradableSpecification.IsUpgradable(subject.Author.QualityProfile,
new List<QualityModel> { file.Quality },
_preferredWordServiceCalculator.Calculate(subject.Author, file.GetSceneOrFileName()),
subject.ParsedBookInfo.Quality,
subject.PreferredWordScore))
{
return Decision.Reject("Existing file on disk is of equal or higher preference: {0}", file.Quality);
if (!_upgradableSpecification.IsUpgradable(subject.Author.QualityProfile,
currentQualities,
_preferredWordServiceCalculator.Calculate(subject.Author, file.GetSceneOrFileName(), subject.Release?.IndexerId ?? 0),
subject.ParsedBookInfo.Quality,
subject.PreferredWordScore))
{
return Decision.Reject("Existing files on disk is of equal or higher preference: {0}", currentQualities.ConcatToString());
}
}
}

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Download.Aggregation.Aggregators
public RemoteBook Aggregate(RemoteBook remoteBook)
{
remoteBook.PreferredWordScore = _preferredWordServiceCalculator.Calculate(remoteBook.Author, remoteBook.Release.Title);
remoteBook.PreferredWordScore = _preferredWordServiceCalculator.Calculate(remoteAlbum.Author, remoteBook.Release.Title, remoteBook.Release.IndexerId);
return remoteBook;
}

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Core.Profiles.Releases
{
public interface IPreferredWordService
{
int Calculate(Author author, string title);
int Calculate(Author author, string title, int indexerId);
List<string> GetMatchingPreferredWords(Author author, string title);
}
@@ -25,11 +25,11 @@ namespace NzbDrone.Core.Profiles.Releases
_logger = logger;
}
public int Calculate(Author series, string title)
public int Calculate(Author series, string title, int indexerId)
{
_logger.Trace("Calculating preferred word score for '{0}'", title);
var releaseProfiles = _releaseProfileService.AllForTags(series.Tags);
var releaseProfiles = _releaseProfileService.EnabledForTags(artist.Tags, indexerId);
var matchingPairs = new List<KeyValuePair<string, int>>();
foreach (var releaseProfile in releaseProfiles)
@@ -54,7 +54,7 @@ namespace NzbDrone.Core.Profiles.Releases
public List<string> GetMatchingPreferredWords(Author author, string title)
{
var releaseProfiles = _releaseProfileService.AllForTags(author.Tags);
var releaseProfiles = _releaseProfileService.EnabledForTags(author.Tags, 0);
var matchingPairs = new List<KeyValuePair<string, int>>();
_logger.Trace("Calculating preferred word score for '{0}'", title);

View File

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

View File

@@ -10,6 +10,7 @@ namespace NzbDrone.Core.Profiles.Releases
List<ReleaseProfile> All();
List<ReleaseProfile> AllForTag(int tagId);
List<ReleaseProfile> AllForTags(HashSet<int> tagIds);
List<ReleaseProfile> EnabledForTags(HashSet<int> tagIds, int indexerId);
ReleaseProfile Get(int id);
void Delete(int id);
ReleaseProfile Add(ReleaseProfile restriction);
@@ -48,6 +49,13 @@ namespace NzbDrone.Core.Profiles.Releases
return _repo.All().Where(r => r.Tags.Intersect(tagIds).Any() || r.Tags.Empty()).ToList();
}
public List<ReleaseProfile> EnabledForTags(HashSet<int> tagIds, int indexerId)
{
return AllForTags(tagIds)
.Where(r => r.Enabled)
.Where(r => r.IndexerId == indexerId || r.IndexerId == 0).ToList();
}
public ReleaseProfile Get(int id)
{
return _repo.Get(id);

View File

@@ -9,6 +9,7 @@ namespace NzbDrone.Core.ThingiProvider
{
List<TProviderDefinition> All();
List<TProvider> GetAvailableProviders();
bool Exists(int id);
TProviderDefinition Get(int id);
TProviderDefinition Create(TProviderDefinition definition);
void Update(TProviderDefinition definition);

View File

@@ -91,6 +91,11 @@ namespace NzbDrone.Core.ThingiProvider
return Active().Select(GetInstance).ToList();
}
public bool Exists(int id)
{
return _providerRepository.Find(id) != null;
}
public TProviderDefinition Get(int id)
{
return _providerRepository.Get(id);

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Profiles.Releases;
using Readarr.Http;
@@ -9,10 +10,12 @@ namespace Readarr.Api.V1.Profiles.Release
public class ReleaseProfileModule : ReadarrRestModule<ReleaseProfileResource>
{
private readonly IReleaseProfileService _releaseProfileService;
private readonly IIndexerFactory _indexerFactory;
public ReleaseProfileModule(IReleaseProfileService releaseProfileService)
public ReleaseProfileModule(IReleaseProfileService releaseProfileService, IIndexerFactory indexerFactory)
{
_releaseProfileService = releaseProfileService;
_indexerFactory = indexerFactory;
GetResourceById = GetById;
GetResourceAll = GetAll;
@@ -26,6 +29,11 @@ namespace Readarr.Api.V1.Profiles.Release
{
context.AddFailure("Either 'Must contain' or 'Must not contain' is required");
}
if (restriction.Enabled && restriction.IndexerId != 0 && !_indexerFactory.Exists(restriction.IndexerId))
{
context.AddFailure(nameof(ReleaseProfile.IndexerId), "Indexer does not exist");
}
});
}

View File

@@ -7,10 +7,12 @@ namespace Readarr.Api.V1.Profiles.Release
{
public class ReleaseProfileResource : RestResource
{
public bool Enabled { get; set; }
public string Required { get; set; }
public string Ignored { get; set; }
public List<KeyValuePair<string, int>> Preferred { get; set; }
public bool IncludePreferredWhenRenaming { get; set; }
public int IndexerId { get; set; }
public HashSet<int> Tags { get; set; }
public ReleaseProfileResource()
@@ -32,10 +34,12 @@ namespace Readarr.Api.V1.Profiles.Release
{
Id = model.Id,
Enabled = model.Enabled,
Required = model.Required,
Ignored = model.Ignored,
Preferred = model.Preferred,
IncludePreferredWhenRenaming = model.IncludePreferredWhenRenaming,
IndexerId = model.IndexerId,
Tags = new HashSet<int>(model.Tags)
};
}
@@ -51,10 +55,12 @@ namespace Readarr.Api.V1.Profiles.Release
{
Id = resource.Id,
Enabled = resource.Enabled,
Required = resource.Required,
Ignored = resource.Ignored,
Preferred = resource.Preferred,
IncludePreferredWhenRenaming = resource.IncludePreferredWhenRenaming,
IndexerId = resource.IndexerId,
Tags = new HashSet<int>(resource.Tags)
};
}