parent
ac709c39ab
commit
853f25468c
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
{
|
{
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue