mirror of https://github.com/Radarr/Radarr
New: Required/Ignored restrictions now support /pattern/ regular expressions
Co-Authored-By: taloth <taloth@users.noreply.github.com>
This commit is contained in:
parent
a771871cf8
commit
328477a1c6
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue