New: Required terms assignable to series via tags New: Ignored terms assignable to series via tagsspull/4/head
parent
d6ed475c63
commit
53c2962d2a
@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class NotRestrictedReleaseSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NotRestrictedReleaseSpecification(IConfigService configService, Logger logger)
|
||||
{
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type { get { return RejectionType.Permanent; } }
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
_logger.Debug("Checking if release contains any restricted terms: {0}", subject);
|
||||
|
||||
var restrictionsString = _configService.ReleaseRestrictions;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(restrictionsString))
|
||||
{
|
||||
_logger.Debug("No restrictions configured, allowing: {0}", subject);
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
var restrictions = restrictionsString.Split(new []{ '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var restriction in restrictions)
|
||||
{
|
||||
if (subject.Release.Title.ToLowerInvariant().Contains(restriction.ToLowerInvariant()))
|
||||
{
|
||||
_logger.Debug("{0} is restricted: {1}", subject, restriction);
|
||||
return Decision.Reject("Contains restricted term: {0}", restriction);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("No restrictions apply, allowing: {0}", subject);
|
||||
return Decision.Accept();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Restrictions;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications
|
||||
{
|
||||
public class ReleaseRestrictionsSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly IRestrictionService _restrictionService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public ReleaseRestrictionsSpecification(IRestrictionService restrictionService, Logger logger)
|
||||
{
|
||||
_restrictionService = restrictionService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RejectionType Type { get { return RejectionType.Permanent; } }
|
||||
|
||||
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
_logger.Debug("Checking if release meets restrictions: {0}", subject);
|
||||
|
||||
var title = subject.Release.Title;
|
||||
var restrictions = _restrictionService.AllForTags(subject.Series.Tags);
|
||||
|
||||
var required = restrictions.Where(r => r.Required.IsNotNullOrWhiteSpace());
|
||||
var ignored = restrictions.Where(r => r.Ignored.IsNotNullOrWhiteSpace());
|
||||
|
||||
foreach (var r in required)
|
||||
{
|
||||
var split = r.Required.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
|
||||
if (!ContainsAny(split, title))
|
||||
{
|
||||
_logger.Debug("[{0}] does not contain one of the required terms: {1}", title, r.Required);
|
||||
return Decision.Reject("Does not contain one of the required terms: {0}", r.Required);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var r in ignored)
|
||||
{
|
||||
var split = r.Ignored.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
|
||||
if (ContainsAny(split, title))
|
||||
{
|
||||
_logger.Debug("[{0}] contains one or more ignored terms: {1}", title, r.Ignored);
|
||||
return Decision.Reject("Contains one or more ignored terms: {0}", r.Ignored);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("[{0}] No restrictions apply, allowing", subject);
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
private static Boolean ContainsAny(List<String> terms, String title)
|
||||
{
|
||||
return terms.Any(t => title.ToLowerInvariant().Contains(t.ToLowerInvariant()));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
<div id="x-indexers-region"></div>
|
||||
<div class="form-horizontal">
|
||||
<div id="x-indexer-options-region"></div>
|
||||
<div id="x-restriction-region"></div>
|
||||
</div>
|
||||
|
@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
define([
|
||||
'backbone',
|
||||
'Settings/Indexers/Restriction/RestrictionModel'
|
||||
], function (Backbone, RestrictionModel) {
|
||||
|
||||
return Backbone.Collection.extend({
|
||||
model : RestrictionModel,
|
||||
url : window.NzbDrone.ApiRoot + '/Restriction'
|
||||
});
|
||||
});
|
@ -0,0 +1,24 @@
|
||||
<fieldset class="advanced-setting">
|
||||
<legend>Restrictions</legend>
|
||||
|
||||
<div class="col-md-12">
|
||||
<div class="rule-setting-list">
|
||||
<div class="rule-setting-header x-header hidden-xs">
|
||||
<div class="row">
|
||||
<span class="col-sm-4">Must Contain</span>
|
||||
<span class="col-sm-4">Must Not Contain</span>
|
||||
<span class="col-sm-3">Tags</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rows x-rows">
|
||||
</div>
|
||||
<div class="rule-setting-footer">
|
||||
<div class="pull-right">
|
||||
<span class="add-rule-setting-mapping">
|
||||
<i class="icon-nd-add x-add" title="Add new restriction" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'vent',
|
||||
'marionette'
|
||||
], function (vent, Marionette) {
|
||||
return Marionette.ItemView.extend({
|
||||
template: 'Settings/Indexers/Restriction/RestrictionDeleteViewTemplate',
|
||||
|
||||
events: {
|
||||
'click .x-confirm-delete': '_delete'
|
||||
},
|
||||
|
||||
_delete: function () {
|
||||
this.model.destroy({
|
||||
wait : true,
|
||||
success: function () {
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,13 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>Delete Restriction</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete the restriction for '{{localPath}}'?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal">cancel</button>
|
||||
<button class="btn btn-danger x-confirm-delete">delete</button>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,60 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
{{#if id}}
|
||||
<h3>Edit Restriction</h3>
|
||||
{{else}}
|
||||
<h3>Add Restriction</h3>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="modal-body remotepath-mapping-modal">
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Must contain</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-5 help-inline">
|
||||
<i class="icon-nd-form-info" title="The release must contain at least one of these terms (case insensitive)" />
|
||||
</div>
|
||||
|
||||
<div class="col-sm-5 col-sm-pull-1">
|
||||
<input type="text" name="required" class="form-control x-required"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Must not contain</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-5 help-inline">
|
||||
<i class="icon-nd-form-info" title="The release will be rejected if it contains one or more of terms (case insensitive)" />
|
||||
</div>
|
||||
|
||||
<div class="col-sm-5 col-sm-pull-1">
|
||||
<input type="text" name="ignored" class="form-control x-ignored"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Tags</label>
|
||||
|
||||
<div class="col-sm-1 col-sm-push-5 help-inline">
|
||||
<i class="icon-nd-form-info" title="Restrictions will apply to series with more or more matching tags. Leave blank to apply to all series" />
|
||||
</div>
|
||||
|
||||
<div class="col-sm-5 col-sm-pull-1">
|
||||
<input type="text" class="form-control x-tags">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{{#if id}}
|
||||
<button class="btn btn-danger pull-left x-delete">delete</button>
|
||||
{{/if}}
|
||||
|
||||
<button class="btn" data-dismiss="modal">cancel</button>
|
||||
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary x-save">save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,12 @@
|
||||
<span class="col-sm-4">
|
||||
{{genericTagDisplay required 'label label-success'}}
|
||||
</span>
|
||||
<span class="col-sm-4">
|
||||
{{genericTagDisplay ignored 'label label-danger'}}
|
||||
</span>
|
||||
<span class="col-sm-3">
|
||||
{{tagDisplay tags}}
|
||||
</span>
|
||||
<span class="col-sm-1">
|
||||
<div class="pull-right"><i class="icon-nd-edit x-edit" title="" data-original-title="Edit"></i></div>
|
||||
</span>
|
@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'backbone.deepmodel'
|
||||
], function ($, DeepModel) {
|
||||
return DeepModel.DeepModel.extend({
|
||||
|
||||
});
|
||||
});
|
@ -1,20 +1,32 @@
|
||||
'use strict';
|
||||
define(
|
||||
[
|
||||
'underscore',
|
||||
'handlebars',
|
||||
'Tags/TagCollection'
|
||||
], function (Handlebars, TagCollection) {
|
||||
], function (_, Handlebars, TagCollection) {
|
||||
|
||||
Handlebars.registerHelper('tagInput', function () {
|
||||
Handlebars.registerHelper('tagDisplay', function (tags) {
|
||||
|
||||
var unit = 'days';
|
||||
var age = this.age;
|
||||
var tagLabels = _.map(TagCollection.filter(function (tag) {
|
||||
return _.contains(tags, tag.get('id'));
|
||||
}), function (tag){
|
||||
return '<span class="label label-info">{0}</span>'.format(tag.get('label'));
|
||||
});
|
||||
|
||||
if (age < 2) {
|
||||
unit = 'hours';
|
||||
age = parseFloat(this.ageHours).toFixed(1);
|
||||
return new Handlebars.SafeString(tagLabels.join(' '));
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('genericTagDisplay', function (tags, classes) {
|
||||
|
||||
if (!tags) {
|
||||
return new Handlebars.SafeString('');
|
||||
}
|
||||
|
||||
return new Handlebars.SafeString('<dt>Age (when grabbed):</dt><dd>{0} {1}</dd>'.format(age, unit));
|
||||
var tagLabels = _.map(tags.split(','), function (tag) {
|
||||
return '<span class="{0}">{1}</span>'.format(classes, tag);
|
||||
});
|
||||
|
||||
return new Handlebars.SafeString(tagLabels.join(' '));
|
||||
});
|
||||
});
|
||||
|
@ -1 +0,0 @@
|
||||
<input type=""/>
|
Loading…
Reference in new issue