New: Required/Ignored restrictions now support /pattern/ regular expressions

Co-Authored-By: taloth <taloth@users.noreply.github.com>
This commit is contained in:
Qstick 2019-08-05 20:47:46 -04:00
parent a771871cf8
commit 328477a1c6
5 changed files with 157 additions and 5 deletions

View File

@ -29,6 +29,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Title = "Dexter.S08E01.EDITED.WEBRip.x264-KYR"
}
};
Mocker.SetConstant<ITermMatcher>(Mocker.Resolve<TermMatcher>());
}
private void GivenRestictions(string required, string ignored)
@ -123,5 +125,16 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
}
[TestCase("/WEB/", true)]
[TestCase("/WEB\b/", false)]
[TestCase("/WEb/", false)]
[TestCase(@"/\.WEB/", true)]
public void should_match_perl_regex(string pattern, bool expected)
{
GivenRestictions(pattern, null);
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().Be(expected);
}
}
}

View File

@ -11,13 +11,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class ReleaseRestrictionsSpecification : IDecisionEngineSpecification
{
private readonly IRestrictionService _restrictionService;
private readonly Logger _logger;
private readonly IRestrictionService _restrictionService;
private readonly ITermMatcher _termMatcher;
public ReleaseRestrictionsSpecification(IRestrictionService restrictionService, Logger logger)
public ReleaseRestrictionsSpecification(ITermMatcher termMatcher, IRestrictionService restrictionService, Logger logger)
{
_restrictionService = restrictionService;
_logger = logger;
_restrictionService = restrictionService;
_termMatcher = termMatcher;
}
public SpecificationPriority Priority => SpecificationPriority.Default;
@ -63,9 +65,9 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return Decision.Accept();
}
private static List<string> ContainsAny(List<string> terms, string title)
private List<string> ContainsAny(List<string> terms, string title)
{
return terms.Where(t => title.ToLowerInvariant().Contains(t.ToLowerInvariant())).ToList();
return terms.Where(t => _termMatcher.IsMatch(t, title)).ToList();
}
}
}

View File

@ -1201,9 +1201,11 @@
<Compile Include="Queue\Queue.cs" />
<Compile Include="Queue\QueueService.cs" />
<Compile Include="Queue\QueueUpdatedEvent.cs" />
<Compile Include="Restrictions\PerlRegexFactory.cs" />
<Compile Include="Restrictions\Restriction.cs" />
<Compile Include="Restrictions\RestrictionRepository.cs" />
<Compile Include="Restrictions\RestrictionService.cs" />
<Compile Include="Restrictions\TermMatcher.cs" />
<Compile Include="Rest\JsonNetSerializer.cs" />
<Compile Include="Rest\RestClientFactory.cs" />
<Compile Include="Rest\RestException.cs" />

View File

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Restrictions
{
public static class PerlRegexFactory
{
private static Regex _perlRegexFormat = new Regex(@"/(?<pattern>.*)/(?<modifiers>[a-z]*)", RegexOptions.Compiled);
public static bool TryCreateRegex(string pattern, out Regex regex)
{
var match = _perlRegexFormat.Match(pattern);
if (!match.Success)
{
regex = null;
return false;
}
regex = CreateRegex(match.Groups["pattern"].Value, match.Groups["modifiers"].Value);
return true;
}
public static Regex CreateRegex(string pattern, string modifiers)
{
var options = GetOptions(modifiers);
// For now we simply expect the pattern to be .net compliant. We should probably check and reject perl-specific constructs.
return new Regex(pattern, options | RegexOptions.Compiled);
}
private static RegexOptions GetOptions(string modifiers)
{
var options = RegexOptions.None;
foreach (var modifier in modifiers)
{
switch (modifier)
{
case 'm':
options |= RegexOptions.Multiline;
break;
case 's':
options |= RegexOptions.Singleline;
break;
case 'i':
options |= RegexOptions.IgnoreCase;
break;
case 'x':
options |= RegexOptions.IgnorePatternWhitespace;
break;
case 'n':
options |= RegexOptions.ExplicitCapture;
break;
default:
throw new ArgumentException("Unknown or unsupported perl regex modifier: " + modifier);
}
}
return options;
}
}
}

View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NzbDrone.Common.Cache;
namespace NzbDrone.Core.Restrictions
{
public interface ITermMatcher
{
bool IsMatch(string term, string value);
}
public class TermMatcher : ITermMatcher
{
private ICached<Predicate<string>> _matcherCache;
public TermMatcher(ICacheManager cacheManager)
{
_matcherCache = cacheManager.GetCache<Predicate<string>>(GetType());
}
public bool IsMatch(string term, string value)
{
return GetMatcher(term)(value);
}
public Predicate<string> GetMatcher(string term)
{
return _matcherCache.Get(term, () => CreateMatcherInternal(term), TimeSpan.FromHours(24));
}
private Predicate<string> CreateMatcherInternal(string term)
{
Regex regex;
if (PerlRegexFactory.TryCreateRegex(term, out regex))
{
return regex.IsMatch;
}
else
{
return new CaseInsensitiveTermMatcher(term).IsMatch;
}
}
private sealed class CaseInsensitiveTermMatcher
{
private readonly string _term;
public CaseInsensitiveTermMatcher(string term)
{
_term = term.ToLowerInvariant();
}
public bool IsMatch(string value)
{
return value.ToLowerInvariant().Contains(_term);
}
}
}
}