diff --git a/src/NzbDrone.Api/Config/IndexerConfigResource.cs b/src/NzbDrone.Api/Config/IndexerConfigResource.cs
index aeb9706e3..e902b538f 100644
--- a/src/NzbDrone.Api/Config/IndexerConfigResource.cs
+++ b/src/NzbDrone.Api/Config/IndexerConfigResource.cs
@@ -7,6 +7,5 @@ public class IndexerConfigResource : RestResource
{
public Int32 Retention { get; set; }
public Int32 RssSyncInterval { get; set; }
- public String ReleaseRestrictions { get; set; }
}
}
diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj
index aa4e9dbc3..03681c89b 100644
--- a/src/NzbDrone.Api/NzbDrone.Api.csproj
+++ b/src/NzbDrone.Api/NzbDrone.Api.csproj
@@ -197,6 +197,8 @@
+
+
diff --git a/src/NzbDrone.Api/Restrictions/RestrictionModule.cs b/src/NzbDrone.Api/Restrictions/RestrictionModule.cs
new file mode 100644
index 000000000..376db6e13
--- /dev/null
+++ b/src/NzbDrone.Api/Restrictions/RestrictionModule.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using NzbDrone.Api.Mapping;
+using NzbDrone.Core.Restrictions;
+
+namespace NzbDrone.Api.Restrictions
+{
+ public class RestrictionModule : NzbDroneRestModule
+ {
+ private readonly IRestrictionService _restrictionService;
+
+
+ public RestrictionModule(IRestrictionService restrictionService)
+ {
+ _restrictionService = restrictionService;
+
+ GetResourceById = Get;
+ GetResourceAll = GetAll;
+ CreateResource = Create;
+ UpdateResource = Update;
+ DeleteResource = Delete;
+ }
+
+ private RestrictionResource Get(Int32 id)
+ {
+ return _restrictionService.Get(id).InjectTo();
+ }
+
+ private List GetAll()
+ {
+ return ToListResource(_restrictionService.All);
+ }
+
+ private Int32 Create(RestrictionResource resource)
+ {
+ return _restrictionService.Add(resource.InjectTo()).Id;
+ }
+
+ private void Update(RestrictionResource resource)
+ {
+ _restrictionService.Update(resource.InjectTo());
+ }
+
+ private void Delete(Int32 id)
+ {
+ _restrictionService.Delete(id);
+ }
+ }
+}
diff --git a/src/NzbDrone.Api/Restrictions/RestrictionResource.cs b/src/NzbDrone.Api/Restrictions/RestrictionResource.cs
new file mode 100644
index 000000000..4e0f71db1
--- /dev/null
+++ b/src/NzbDrone.Api/Restrictions/RestrictionResource.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using NzbDrone.Api.REST;
+
+namespace NzbDrone.Api.Restrictions
+{
+ public class RestrictionResource : RestResource
+ {
+ public String Required { get; set; }
+ public String Preferred { get; set; }
+ public String Ignored { get; set; }
+ public HashSet Tags { get; set; }
+
+ public RestrictionResource()
+ {
+ Tags = new HashSet();
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/IEnumerableExtensions.cs b/src/NzbDrone.Common/IEnumerableExtensions.cs
index 4ccbcae6d..502086c27 100644
--- a/src/NzbDrone.Common/IEnumerableExtensions.cs
+++ b/src/NzbDrone.Common/IEnumerableExtensions.cs
@@ -27,5 +27,15 @@ public static bool Empty(this IEnumerable source)
{
return !source.Any();
}
+
+ public static bool None(this IEnumerable source, Func predicate)
+ {
+ return !source.Any(predicate);
+ }
+
+ public static bool NotAll(this IEnumerable source, Func predicate)
+ {
+ return !source.All(predicate);
+ }
}
}
\ No newline at end of file
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs
deleted file mode 100644
index c0cab92c3..000000000
--- a/src/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using FluentAssertions;
-using NUnit.Framework;
-using NzbDrone.Core.Configuration;
-using NzbDrone.Core.DecisionEngine.Specifications;
-using NzbDrone.Core.Parser.Model;
-using NzbDrone.Core.Test.Framework;
-
-namespace NzbDrone.Core.Test.DecisionEngineTests
-{
- [TestFixture]
- public class NotRestrictedReleaseSpecificationFixture : CoreTest
- {
- private RemoteEpisode _parseResult;
-
- [SetUp]
- public void Setup()
- {
- _parseResult = new RemoteEpisode
- {
- Release = new ReleaseInfo
- {
- Title = "Dexter.S08E01.EDITED.WEBRip.x264-KYR"
- }
- };
- }
-
- [Test]
- public void should_be_true_when_restrictions_are_empty()
- {
- Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue();
- }
-
- [TestCase("KYR")]
- [TestCase("EDITED")]
- [TestCase("edited")]
- [TestCase("2HD\nKYR")]
- [TestCase("2HD\nkyr")]
- public void should_be_false_when_nzb_contains_a_restricted_term(string restrictions)
- {
- Mocker.GetMock().SetupGet(c => c.ReleaseRestrictions).Returns(restrictions);
- Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeFalse();
- }
-
- [TestCase("NotReal")]
- [TestCase("LoL")]
- [TestCase("Hello\nWorld")]
- public void should_be_true_when_nzb_does_not_contain_a_restricted_term(string restrictions)
- {
- Mocker.GetMock().SetupGet(c => c.ReleaseRestrictions).Returns(restrictions);
- Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue();
- }
-
- [Test]
- public void should_not_try_to_find_empty_string_as_a_match()
- {
- Mocker.GetMock().SetupGet(c => c.ReleaseRestrictions).Returns("test\n");
- Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue();
- }
- }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/ReleaseRestrictionsSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/ReleaseRestrictionsSpecificationFixture.cs
new file mode 100644
index 000000000..0beb24e40
--- /dev/null
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/ReleaseRestrictionsSpecificationFixture.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using FluentAssertions;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Core.Configuration;
+using NzbDrone.Core.DecisionEngine.Specifications;
+using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.Restrictions;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Core.Tv;
+
+namespace NzbDrone.Core.Test.DecisionEngineTests
+{
+ [TestFixture]
+ public class ReleaseRestrictionsSpecificationFixture : CoreTest
+ {
+ private RemoteEpisode _parseResult;
+
+ [SetUp]
+ public void Setup()
+ {
+ _parseResult = new RemoteEpisode
+ {
+ Series = new Series
+ {
+ Tags = new HashSet()
+ },
+ Release = new ReleaseInfo
+ {
+ Title = "Dexter.S08E01.EDITED.WEBRip.x264-KYR"
+ }
+ };
+ }
+
+ private void GivenRestictions(String required, String ignored)
+ {
+ Mocker.GetMock()
+ .Setup(s => s.AllForTags(It.IsAny>()))
+ .Returns(new List
+ {
+ new Restriction
+ {
+ Required = required,
+ Ignored = ignored
+ }
+ });
+ }
+
+ [Test]
+ public void should_be_true_when_restrictions_are_empty()
+ {
+ Mocker.GetMock()
+ .Setup(s => s.AllForTags(It.IsAny>()))
+ .Returns(new List());
+
+ Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue();
+ }
+
+ [Test]
+ public void should_be_true_when_title_contains_one_required_term()
+ {
+ GivenRestictions("WEBRip", null);
+
+ Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue();
+ }
+
+ [Test]
+ public void should_be_false_when_title_does_not_contain_any_required_terms()
+ {
+ GivenRestictions("doesnt,exist", null);
+
+ Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeFalse();
+ }
+
+ [Test]
+ public void should_be_true_when_title_does_not_contain_any_ignored_terms()
+ {
+ GivenRestictions(null, "ignored");
+
+ Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue();
+ }
+
+ [Test]
+ public void should_be_false_when_title_contains_one_anded_ignored_terms()
+ {
+ GivenRestictions(null, "edited");
+
+ Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeFalse();
+ }
+
+ [TestCase("EdiTED")]
+ [TestCase("webrip")]
+ [TestCase("X264")]
+ [TestCase("X264,NOTTHERE")]
+ public void should_ignore_case_when_matching_required(String required)
+ {
+ GivenRestictions(required, null);
+
+ Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue();
+ }
+
+ [TestCase("EdiTED")]
+ [TestCase("webrip")]
+ [TestCase("X264")]
+ [TestCase("X264,NOTTHERE")]
+ public void should_ignore_case_when_matching_ignored(String ignored)
+ {
+ GivenRestictions(null, ignored);
+
+ Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeFalse();
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 65f3eba77..91dde053d 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -129,7 +129,7 @@
-
+
diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs
index 06068e5db..8293d066a 100644
--- a/src/NzbDrone.Core/Configuration/ConfigService.cs
+++ b/src/NzbDrone.Core/Configuration/ConfigService.cs
@@ -7,8 +7,6 @@
using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Messaging.Events;
-using NzbDrone.Core.Update;
-
namespace NzbDrone.Core.Configuration
{
@@ -103,12 +101,6 @@ public string RecycleBin
set { SetValue("RecycleBin", value); }
}
- public string ReleaseRestrictions
- {
- get { return GetValue("ReleaseRestrictions", String.Empty).Trim('\r', '\n'); }
- set { SetValue("ReleaseRestrictions", value.Trim('\r', '\n')); }
- }
-
public Int32 RssSyncInterval
{
get { return GetValueInt("RssSyncInterval", 15); }
diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs
index e21185bb7..5dd81fc4b 100644
--- a/src/NzbDrone.Core/Configuration/IConfigService.cs
+++ b/src/NzbDrone.Core/Configuration/IConfigService.cs
@@ -47,7 +47,6 @@ public interface IConfigService
//Indexers
Int32 Retention { get; set; }
Int32 RssSyncInterval { get; set; }
- String ReleaseRestrictions { get; set; }
//UI
Int32 FirstDayOfWeek { get; set; }
diff --git a/src/NzbDrone.Core/Datastore/Migration/068_add_release_restrictions.cs b/src/NzbDrone.Core/Datastore/Migration/068_add_release_restrictions.cs
new file mode 100644
index 000000000..c643e29ab
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/068_add_release_restrictions.cs
@@ -0,0 +1,49 @@
+using System.Data;
+using FluentMigrator;
+using NzbDrone.Core.Datastore.Migration.Framework;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+ [Migration(68)]
+ public class add_release_restrictions : NzbDroneMigrationBase
+ {
+ protected override void MainDbUpgrade()
+ {
+ Create.TableForModel("Restrictions")
+ .WithColumn("Required").AsString().Nullable()
+ .WithColumn("Preferred").AsString().Nullable()
+ .WithColumn("Ignored").AsString().Nullable()
+ .WithColumn("Tags").AsString().NotNullable();
+
+ Execute.WithConnection(ConvertRestrictions);
+ Execute.Sql("DELETE FROM Config WHERE [Key] = 'releaserestrictions'");
+ }
+
+ private void ConvertRestrictions(IDbConnection conn, IDbTransaction tran)
+ {
+ using (IDbCommand getRestictionsCmd = conn.CreateCommand())
+ {
+ getRestictionsCmd.Transaction = tran;
+ getRestictionsCmd.CommandText = @"SELECT [Value] FROM Config WHERE [Key] = 'releaserestrictions'";
+
+ using (IDataReader configReader = getRestictionsCmd.ExecuteReader())
+ {
+ while (configReader.Read())
+ {
+ var restrictions = configReader.GetString(0);
+ restrictions = restrictions.Replace("\n", ",");
+
+ using (IDbCommand insertCmd = conn.CreateCommand())
+ {
+ insertCmd.Transaction = tran;
+ insertCmd.CommandText = "INSERT INTO Restrictions (Ignored, Tags) VALUES ('?', '[]')";
+ insertCmd.AddParameter(restrictions);
+
+ insertCmd.ExecuteNonQuery();
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs
index b30b350ec..875ff2420 100644
--- a/src/NzbDrone.Core/Datastore/TableMapping.cs
+++ b/src/NzbDrone.Core/Datastore/TableMapping.cs
@@ -22,6 +22,7 @@
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
+using NzbDrone.Core.Restrictions;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.SeriesStats;
using NzbDrone.Core.Tags;
@@ -92,6 +93,7 @@ public static void Map()
Mapper.Entity().RegisterModel("RemotePathMappings");
Mapper.Entity().RegisterModel("Tags");
+ Mapper.Entity().RegisterModel("Restrictions");
}
private static void RegisterMappers()
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs
deleted file mode 100644
index deb8428b9..000000000
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs
+++ /dev/null
@@ -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();
- }
- }
-}
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs
new file mode 100644
index 000000000..4a2d2b32b
--- /dev/null
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs
@@ -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 terms, String title)
+ {
+ return terms.Any(t => title.ToLowerInvariant().Contains(t.ToLowerInvariant()));
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index 34b9a5c19..fd11dd1bd 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -230,6 +230,7 @@
+
@@ -263,7 +264,7 @@
-
+
@@ -711,6 +712,9 @@
+
+
+
diff --git a/src/NzbDrone.Core/Restrictions/Restriction.cs b/src/NzbDrone.Core/Restrictions/Restriction.cs
new file mode 100644
index 000000000..c3944f444
--- /dev/null
+++ b/src/NzbDrone.Core/Restrictions/Restriction.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using NzbDrone.Core.Datastore;
+
+namespace NzbDrone.Core.Restrictions
+{
+ public class Restriction : ModelBase
+ {
+ public String Required { get; set; }
+ public String Preferred { get; set; }
+ public String Ignored { get; set; }
+ public HashSet Tags { get; set; }
+
+ public Restriction()
+ {
+ Tags = new HashSet();
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Restrictions/RestrictionRepository.cs b/src/NzbDrone.Core/Restrictions/RestrictionRepository.cs
new file mode 100644
index 000000000..98b3a2acc
--- /dev/null
+++ b/src/NzbDrone.Core/Restrictions/RestrictionRepository.cs
@@ -0,0 +1,17 @@
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.Messaging.Events;
+
+namespace NzbDrone.Core.Restrictions
+{
+ public interface IRestrictionRepository : IBasicRepository
+ {
+ }
+
+ public class RestrictionRepository : BasicRepository, IRestrictionRepository
+ {
+ public RestrictionRepository(IDatabase database, IEventAggregator eventAggregator)
+ : base(database, eventAggregator)
+ {
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Restrictions/RestrictionService.cs b/src/NzbDrone.Core/Restrictions/RestrictionService.cs
new file mode 100644
index 000000000..c06341b51
--- /dev/null
+++ b/src/NzbDrone.Core/Restrictions/RestrictionService.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NLog;
+using NzbDrone.Common;
+
+namespace NzbDrone.Core.Restrictions
+{
+ public interface IRestrictionService
+ {
+ List All();
+ List AllForTag(Int32 tagId);
+ List AllForTags(HashSet tagIds);
+ Restriction Get(Int32 id);
+ void Delete(Int32 id);
+ Restriction Add(Restriction restriction);
+ Restriction Update(Restriction restriction);
+ }
+
+ public class RestrictionService : IRestrictionService
+ {
+ private readonly IRestrictionRepository _repo;
+ private readonly Logger _logger;
+
+ public RestrictionService(IRestrictionRepository repo, Logger logger)
+ {
+ _repo = repo;
+ _logger = logger;
+ }
+
+ public List All()
+ {
+ return _repo.All().ToList();
+ }
+
+ public List AllForTag(Int32 tagId)
+ {
+ return _repo.All().Where(r => r.Tags.Contains(tagId) || r.Tags.Empty()).ToList();
+ }
+
+ public List AllForTags(HashSet tagIds)
+ {
+ return _repo.All().Where(r => r.Tags.Intersect(tagIds).Any() || r.Tags.Empty()).ToList();
+ }
+
+ public Restriction Get(Int32 id)
+ {
+ return _repo.Get(id);
+ }
+
+ public void Delete(Int32 id)
+ {
+ _repo.Delete(id);
+ }
+
+ public Restriction Add(Restriction restriction)
+ {
+ return _repo.Insert(restriction);
+ }
+
+ public Restriction Update(Restriction restriction)
+ {
+ return _repo.Update(restriction);
+ }
+ }
+}
diff --git a/src/UI/Mixins/TagInput.js b/src/UI/Mixins/TagInput.js
index ce5315605..9c6dd81dd 100644
--- a/src/UI/Mixins/TagInput.js
+++ b/src/UI/Mixins/TagInput.js
@@ -9,12 +9,14 @@ define(
], function ($, _, TagCollection, TagModel) {
var originalAdd = $.fn.tagsinput.Constructor.prototype.add;
+ var originalRemove = $.fn.tagsinput.Constructor.prototype.remove;
+ var originalBuild = $.fn.tagsinput.Constructor.prototype.build;
$.fn.tagsinput.Constructor.prototype.add = function (item, dontPushVal) {
var self = this;
var tagLimitations = new RegExp('[^-_a-z0-9]', 'i');
- if (typeof item === 'string') {
+ if (typeof item === 'string' && this.options.tag) {
if (item === null || item === '' || tagLimitations.test(item)) {
return;
@@ -42,6 +44,34 @@ define(
}
};
+ $.fn.tagsinput.Constructor.prototype.remove = function (item, dontPushVal) {
+ if (item === null) {
+ return;
+ }
+
+ originalRemove.call(this, item, dontPushVal);
+ };
+
+ $.fn.tagsinput.Constructor.prototype.build = function (options) {
+ var self = this;
+ var defaults = {
+ confirmKeys : [9, 13, 32, 44, 59] //tab, enter, space, comma, semi-colon
+ };
+
+ options = $.extend({}, defaults, options);
+
+ self.$input.on('keydown', function (event) {
+ if (event.which === 9) {
+ var e = $.Event('keypress');
+ e.which = 9;
+ self.$input.trigger(e);
+ event.preventDefault();
+ }
+ });
+
+ originalBuild.call(this, options);
+ };
+
$.fn.tagInput = function (options) {
var input = this;
var model = options.model;
@@ -49,10 +79,11 @@ define(
var tags = getExistingTags(model.get(property));
var tagInput = $(this).tagsinput({
- freeInput: true,
- itemValue : 'id',
- itemText : 'label',
- trimValue : true,
+ tag : true,
+ freeInput : true,
+ itemValue : 'id',
+ itemText : 'label',
+ trimValue : true,
typeaheadjs : {
name: 'tags',
displayKey: 'label',
diff --git a/src/UI/Series/Edit/EditSeriesView.js b/src/UI/Series/Edit/EditSeriesView.js
index 963512ff4..f56876310 100644
--- a/src/UI/Series/Edit/EditSeriesView.js
+++ b/src/UI/Series/Edit/EditSeriesView.js
@@ -24,7 +24,6 @@ define(
'click .x-remove': '_removeSeries'
},
-
initialize: function () {
this.model.set('profiles', Profiles);
},
diff --git a/src/UI/Settings/DownloadClient/RemotePathMapping/RemotePathMappingCollectionViewTemplate.hbs b/src/UI/Settings/DownloadClient/RemotePathMapping/RemotePathMappingCollectionViewTemplate.hbs
index 846dee402..ec332b59f 100644
--- a/src/UI/Settings/DownloadClient/RemotePathMapping/RemotePathMappingCollectionViewTemplate.hbs
+++ b/src/UI/Settings/DownloadClient/RemotePathMapping/RemotePathMappingCollectionViewTemplate.hbs
@@ -2,8 +2,8 @@