Added HDBits Category, Codec, and Medium Filtering Capability (#1458)

* Added advanced configuration options to support filtering Categories, Codecs, and Medium to the HDBits indexer.

* Changes to use the existing tags with a controlled vocabulary.

* 1) Sorting select options by name
2) Moved the autocomplete tag code into taginput as requested

* removed commented out line

* require cleanups
This commit is contained in:
randellhodges 2017-05-29 10:56:16 -05:00 committed by Leonardo Galli
parent 86634006e5
commit 3d48da2111
7 changed files with 129 additions and 52 deletions

View File

@ -46,7 +46,7 @@ namespace NzbDrone.Api.ClientSchema
field.Value = value;
}
if (fieldAttribute.Type == FieldType.Select)
if (fieldAttribute.Type == FieldType.Select || fieldAttribute.Type == FieldType.Tag)
{
field.SelectOptions = GetSelectOptions(fieldAttribute.SelectOptions);
}
@ -150,7 +150,7 @@ namespace NzbDrone.Api.ClientSchema
private static List<SelectOption> GetSelectOptions(Type selectOptions)
{
if (selectOptions == typeof(Profile))
if (selectOptions == null || selectOptions == typeof(Profile))
{
return new List<SelectOption>();
}
@ -165,7 +165,7 @@ namespace NzbDrone.Api.ClientSchema
var options = from Enum e in Enum.GetValues(selectOptions)
select new SelectOption { Value = Convert.ToInt32(e), Name = e.ToString() };
return options.OrderBy(o => o.Value).ToList();
return options.OrderBy(o => o.Name).ToList();
}
}
}

View File

@ -59,6 +59,10 @@ namespace NzbDrone.Core.Indexers.HDBits
query.Username = Settings.Username;
query.Passkey = Settings.ApiKey;
query.Category = Settings.Categories.ToArray();
query.Codec = Settings.Codecs.ToArray();
query.Medium = Settings.Mediums.ToArray();
// Require Internal only if came from RSS sync
if (Settings.RequireInternal && query.ImdbInfo == null)
{

View File

@ -1,7 +1,12 @@
using FluentValidation;
using System;
using System.Linq;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using System.Linq.Expressions;
using FluentValidation.Results;
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.HDBits
{
@ -21,6 +26,10 @@ namespace NzbDrone.Core.Indexers.HDBits
public HDBitsSettings()
{
BaseUrl = "https://hdbits.org";
Categories = new int[] { (int)HdBitsCategory.Movie };
Codecs = new int[0];
Mediums = new int[0];
}
[FieldDefinition(0, Label = "Username")]
@ -38,6 +47,15 @@ namespace NzbDrone.Core.Indexers.HDBits
[FieldDefinition(4, Label = "Require Internal", Type = FieldType.Checkbox, HelpText = "Require Internal releases for release to be accepted.")]
public bool RequireInternal { get; set; }
[FieldDefinition(5, Label = "Categories", Type = FieldType.Tag, SelectOptions = typeof(HdBitsCategory), Advanced = true, HelpText = "Options: Movie, TV, Documentary, Music, Sport, Audio, XXX, MiscDemo. If unspecified, all options are used.")]
public IEnumerable<int> Categories { get; set; }
[FieldDefinition(6, Label = "Codecs", Type = FieldType.Tag, SelectOptions = typeof(HdBitsCodec), Advanced = true, HelpText = "Options: h264, Mpeg2, VC1, Xvid. If unspecified, all options are used.")]
public IEnumerable<int> Codecs { get; set; }
[FieldDefinition(7, Label = "Mediums", Type = FieldType.Tag, SelectOptions = typeof(HdBitsMedium), Advanced = true, HelpText = "Options: BluRay, Encode, Capture, Remux, WebDL. If unspecified, all options are used.")]
public IEnumerable<int> Mediums { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@ -1,8 +1,8 @@
<div class="form-group {{#if advanced}}advanced-setting{{/if}}">
<div class="form-group {{#if advanced}}advanced-setting{{/if}}">
<label class="col-sm-3 control-label">{{label}}</label>
<div class="col-sm-5">
<input type="text" name="fields.{{order}}.value" validation-name="{{name}}" class="form-control x-form-tag"/>
<input type="text" name="fields.{{order}}.value" validation-name="{{name}}" tag-source="{{json selectOptions}}" class="form-control x-form-tag"/>
</div>
{{> FormHelpPartial}}

View File

@ -1,7 +1,11 @@
var Handlebars = require('handlebars');
var Handlebars = require('handlebars');
Handlebars.registerHelper('TitleCase', function(input) {
return new Handlebars.SafeString(input.replace(/\w\S*/g, function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}));
});
Handlebars.registerHelper('json', function (obj) {
return JSON.stringify(obj);
});

View File

@ -1,14 +1,14 @@
var $ = require('jquery');
var $ = require('jquery');
var _ = require('underscore');
var TagCollection = require('../Tags/TagCollection');
var TagModel = require('../Tags/TagModel');
require('bootstrap.tagsinput');
var substringMatcher = function(tagCollection) {
var substringMatcher = function(tags, selector) {
return function findMatches (q, cb) {
q = q.replace(/[^-_a-z0-9]/gi, '').toLowerCase();
var matches = _.select(tagCollection.toJSON(), function(tag) {
return tag.label.toLowerCase().indexOf(q) > -1;
var matches = _.select(tags, function(tag) {
return selector(tag).toLowerCase().indexOf(q) > -1;
});
cb(matches);
};
@ -108,49 +108,91 @@ $.fn.tagsinput.Constructor.prototype.build = function(options) {
};
$.fn.tagInput = function(options) {
options = $.extend({}, { allowNew : true }, options);
var input = this;
var model = options.model;
var property = options.property;
this.each(function () {
var tagInput = $(this).tagsinput({
tagCollection : TagCollection,
freeInput : true,
allowNew : options.allowNew,
itemValue : 'id',
itemText : 'label',
trimValue : true,
typeaheadjs : {
name : 'tags',
displayKey : 'label',
source : substringMatcher(TagCollection)
var input = $(this);
var tagInput = null;
if (input[0].hasAttribute('tag-source')) {
var listItems = JSON.parse(input.attr('tag-source'));
tagInput = input.tagsinput({
freeInput: false,
allowNew: false,
allowDuplicates: false,
itemValue: 'value',
itemText: 'name',
typeaheadjs: {
displayKey: 'name',
source: substringMatcher(listItems, function (t) { return t.name; })
}
});
var origValue = input.val();
input.tagsinput('removeAll');
if (origValue) {
_.each(origValue.split(','), function (v) {
var parsed = parseInt(v);
var found = _.find(listItems, function (t) { return t.value === parsed; });
if (found) {
input.tagsinput('add', found);
}
});
}
}
else {
options = $.extend({}, { allowNew: true }, options);
var model = options.model;
var property = options.property;
tagInput = input.tagsinput({
tagCollection: TagCollection,
freeInput: true,
allowNew: options.allowNew,
itemValue: 'id',
itemText: 'label',
trimValue: true,
typeaheadjs: {
name: 'tags',
displayKey: 'label',
source: substringMatcher(TagCollection.toJSON(), function (t) { return t.label; })
}
});
//Override the free input being set to false because we're using objects
$(tagInput)[0].options.freeInput = true;
if (model) {
var tags = getExistingTags(model.get(property));
//Remove any existing tags and re-add them
input.tagsinput('removeAll');
_.each(tags, function (tag) {
input.tagsinput('add', tag);
});
input.tagsinput('refresh');
input.on('itemAdded', function (event) {
var tags = model.get(property);
tags.push(event.item.id);
model.set(property, tags);
});
input.on('itemRemoved', function (event) {
if (!event.item) {
return;
}
var tags = _.without(model.get(property), event.item.id);
model.set(property, tags);
});
}
}
});
//Override the free input being set to false because we're using objects
$(tagInput)[0].options.freeInput = true;
if (model) {
var tags = getExistingTags(model.get(property));
//Remove any existing tags and re-add them
$(this).tagsinput('removeAll');
_.each(tags, function(tag) {
$(input).tagsinput('add', tag);
});
$(this).tagsinput('refresh');
$(this).on('itemAdded', function(event) {
var tags = model.get(property);
tags.push(event.item.id);
model.set(property, tags);
});
$(this).on('itemRemoved', function(event) {
if (!event.item) {
return;
}
var tags = _.without(model.get(property), event.item.id);
model.set(property, tags);
});
}
};

View File

@ -1,4 +1,4 @@
var _ = require('underscore');
var _ = require('underscore');
var $ = require('jquery');
var vent = require('vent');
var Marionette = require('marionette');
@ -8,11 +8,16 @@ var AsValidatedView = require('../../../Mixins/AsValidatedView');
var AsEditModalView = require('../../../Mixins/AsEditModalView');
require('../../../Form/FormBuilder');
require('../../../Mixins/AutoComplete');
require('../../../Mixins/TagInput');
require('bootstrap');
var view = Marionette.ItemView.extend({
template : 'Settings/Indexers/Edit/IndexerEditViewTemplate',
ui: {
tags : '.x-form-tag'
},
events : {
'click .x-back' : '_back',
'click .x-captcha-refresh' : '_onRefreshCaptcha'
@ -24,6 +29,10 @@ var view = Marionette.ItemView.extend({
this.targetCollection = options.targetCollection;
},
onRender: function () {
this.ui.tags.tagInput({});
},
_onAfterSave : function() {
this.targetCollection.add(this.model, { merge : true });
vent.trigger(vent.Commands.CloseModalCommand);