Fixed: Add tag when text box loses focus

This commit is contained in:
Mark McDowall 2014-12-02 17:18:17 -08:00
parent be81bf322a
commit cd1d34a0f2
17 changed files with 373 additions and 49 deletions

View File

@ -100,6 +100,7 @@
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
<Compile Include="Profiles\Delay\DelayProfileModule.cs" />
<Compile Include="Profiles\Delay\DelayProfileResource.cs" />
<Compile Include="Profiles\Delay\DelayProfileValidator.cs" />
<Compile Include="RemotePathMappings\RemotePathMappingModule.cs" />
<Compile Include="RemotePathMappings\RemotePathMappingResource.cs" />
<Compile Include="Config\UiConfigModule.cs" />

View File

@ -24,6 +24,9 @@ namespace NzbDrone.Api.Profiles.Delay
SharedValidator.RuleFor(d => d.Tags).NotEmpty().When(d => d.Id != 1);
SharedValidator.RuleFor(d => d.Tags).EmptyCollection().When(d => d.Id == 1);
SharedValidator.RuleFor(d => d.Tags).SetValidator(tagInUseValidator);
SharedValidator.RuleFor(d => d.UsenetDelay).GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(d => d.TorrentDelay).GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(d => d.Id).SetValidator(new DelayProfileValidator());
}
private int Create(DelayProfileResource resource)

View File

@ -6,6 +6,8 @@ namespace NzbDrone.Api.Profiles.Delay
{
public class DelayProfileResource : RestResource
{
public bool EnableUsenet { get; set; }
public bool EnableTorrent { get; set; }
public DownloadProtocol PreferredProtocol { get; set; }
public int UsenetDelay { get; set; }
public int TorrentDelay { get; set; }

View File

@ -0,0 +1,27 @@
using FluentValidation.Validators;
using NzbDrone.Core.Profiles.Delay;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Profiles.Delay
{
public class DelayProfileValidator : PropertyValidator
{
public DelayProfileValidator()
: base("Usenet or Torrent must be enabled")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var delayProfile = new DelayProfile();
delayProfile.InjectFrom(context.ParentContext.InstanceToValidate);
if (!delayProfile.EnableUsenet && !delayProfile.EnableTorrent)
{
return false;
}
return true;
}
}
}

View File

@ -1,21 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using FluentMigrator;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Tags;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class delay_profileFixture : MigrationTest<delay_profile>
{
[TestCase]
[Test]
public void should_migrate_old_delays()
{
WithTestDb(c =>
@ -27,6 +26,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
Cutoff = "{}",
Items = "{}"
});
c.Insert.IntoTable("Profiles").Row(new
{
GrabDelay = 2,
@ -36,17 +36,72 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var allProfiles = Mocker.Resolve<DelayProfileRepository>().All().ToList();
allProfiles.Should().HaveCount(3);
allProfiles.Should().OnlyContain(c => c.PreferredProtocol == DownloadProtocol.Usenet);
allProfiles.Should().OnlyContain(c => c.TorrentDelay == 0);
allProfiles.Should().Contain(c => c.UsenetDelay == 60);
allProfiles.Should().Contain(c => c.UsenetDelay == 120);
}
[Test]
public void should_create_tag_for_delay_profile()
{
WithTestDb(c =>
c.Insert.IntoTable("Profiles").Row(new
{
GrabDelay = 1,
Name = "OneHour",
Cutoff = "{}",
Items = "{}"
})
);
var tags = Mocker.Resolve<TagRepository>().All().ToList();
tags.Should().HaveCount(1);
tags.First().Label.Should().Be("delay-60");
}
[Test]
public void should_add_tag_to_series_that_had_a_profile_with_delay_attached()
{
WithTestDb(c =>
{
c.Insert.IntoTable("Profiles").Row(new
{
GrabDelay = 1,
Name = "OneHour",
Cutoff = "{}",
Items = "{}"
});
c.Insert.IntoTable("Series").Row(new
{
TvdbId = 0,
TvRageId = 0,
Title = "Series",
TitleSlug = "series",
CleanTitle = "series",
Status = 0,
Images = "[]",
Path = @"C:\Test\Series",
Monitored = 1,
SeasonFolder = 1,
RunTime = 0,
SeriesType = 0,
UseSceneNumbering = 0,
Tags = "[1]"
});
});
var tag = Mocker.Resolve<TagRepository>().All().ToList().First();
var series = Mocker.Resolve<SeriesRepository>().All().ToList();
series.Should().HaveCount(1);
series.First().Tags.Should().HaveCount(1);
series.First().Tags.First().Should().Be(tag.Id);
}
}
}
}

View File

@ -0,0 +1,75 @@
using System.Collections.Generic;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class ProtocolSpecificationFixture : CoreTest<ProtocolSpecification>
{
private RemoteEpisode _remoteEpisode;
private DelayProfile _delayProfile;
[SetUp]
public void Setup()
{
_remoteEpisode = new RemoteEpisode();
_remoteEpisode.Release = new ReleaseInfo();
_remoteEpisode.Series = new Series();
_delayProfile = new DelayProfile();
Mocker.GetMock<IDelayProfileService>()
.Setup(s => s.BestForTags(It.IsAny<HashSet<int>>()))
.Returns(_delayProfile);
}
private void GivenProtocol(DownloadProtocol downloadProtocol)
{
_remoteEpisode.Release.DownloadProtocol = downloadProtocol;
}
[Test]
public void should_be_true_if_usenet_and_usenet_is_enabled()
{
GivenProtocol(DownloadProtocol.Usenet);
_delayProfile.EnableUsenet = true;
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().Be(true);
}
[Test]
public void should_be_true_if_torrent_and_torrent_is_enabled()
{
GivenProtocol(DownloadProtocol.Torrent);
_delayProfile.EnableTorrent = true;
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().Be(true);
}
[Test]
public void should_be_false_if_usenet_and_usenet_is_disabled()
{
GivenProtocol(DownloadProtocol.Usenet);
_delayProfile.EnableUsenet = false;
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().Be(false);
}
[Test]
public void should_be_false_if_torrent_and_torrent_is_disabled()
{
GivenProtocol(DownloadProtocol.Torrent);
_delayProfile.EnableTorrent = false;
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().Be(false);
}
}
}

View File

@ -124,6 +124,7 @@
<Compile Include="Datastore\SqliteSchemaDumperTests\SqliteSchemaDumperFixture.cs" />
<Compile Include="DecisionEngineTests\AcceptableSizeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\AnimeVersionUpgradeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\ProtocolSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\CutoffSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\DownloadDecisionMakerFixture.cs" />
<Compile Include="DecisionEngineTests\HistorySpecificationFixture.cs" />

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Data;
using System.Linq;
using FluentMigrator;
using NzbDrone.Common;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
@ -16,15 +15,18 @@ namespace NzbDrone.Core.Datastore.Migration
protected override void MainDbUpgrade()
{
Create.TableForModel("DelayProfiles")
.WithColumn("EnableUsenet").AsBoolean().NotNullable()
.WithColumn("EnableTorrent").AsBoolean().NotNullable()
.WithColumn("PreferredProtocol").AsInt32().NotNullable()
.WithColumn("UsenetDelay").AsInt32().NotNullable()
.WithColumn("TorrentDelay").AsInt32().NotNullable()
.WithColumn("Order").AsInt32().NotNullable()
.WithColumn("Tags").AsString().NotNullable();
Insert.IntoTable("DelayProfiles").Row(new
{
EnableUsenet = 1,
EnableTorrent = 1,
PreferredProtocol = 1,
UsenetDelay = 0,
TorrentDelay = 0,
@ -55,7 +57,7 @@ namespace NzbDrone.Core.Datastore.Migration
using (IDbCommand insertDelayProfileCmd = conn.CreateCommand())
{
insertDelayProfileCmd.Transaction = tran;
insertDelayProfileCmd.CommandText = "INSERT INTO DelayProfiles (PreferredProtocol, TorrentDelay, UsenetDelay, [Order], Tags) VALUES (1, 0, ?, ?, ?)";
insertDelayProfileCmd.CommandText = "INSERT INTO DelayProfiles (EnableUsenet, EnableTorrent, PreferredProtocol, TorrentDelay, UsenetDelay, [Order], Tags) VALUES (1, 1, 1, 0, ?, ?, ?)";
insertDelayProfileCmd.AddParameter(profile.GrabDelay);
insertDelayProfileCmd.AddParameter(order);
insertDelayProfileCmd.AddParameter(tags);

View File

@ -0,0 +1,45 @@
using NLog;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles.Delay;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class ProtocolSpecification : IDecisionEngineSpecification
{
private readonly IPendingReleaseService _pendingReleaseService;
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
private readonly IDelayProfileService _delayProfileService;
private readonly Logger _logger;
public ProtocolSpecification(IDelayProfileService delayProfileService,
Logger logger)
{
_delayProfileService = delayProfileService;
_logger = logger;
}
public RejectionType Type { get { return RejectionType.Temporary; } }
public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria)
{
var delayProfile = _delayProfileService.BestForTags(subject.Series.Tags);
if (subject.Release.DownloadProtocol == DownloadProtocol.Usenet && !delayProfile.EnableUsenet)
{
_logger.Debug("[{0}] Usenet is not enabled for this series", subject.Release.Title);
return Decision.Reject("Usenet is not enabled for this series");
}
if (subject.Release.DownloadProtocol == DownloadProtocol.Torrent && !delayProfile.EnableTorrent)
{
_logger.Debug("[{0}] Torrent is not enabled for this series", subject.Release.Title);
return Decision.Reject("Torrent is not enabled for this series");
}
return Decision.Accept();
}
}
}

View File

@ -261,6 +261,7 @@
<Compile Include="DecisionEngine\Specifications\BlacklistSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\AnimeVersionUpgradeSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\CutoffSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\ProtocolSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\LanguageSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\NotInQueueSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\ReleaseRestrictionsSpecification.cs" />

View File

@ -6,6 +6,8 @@ namespace NzbDrone.Core.Profiles.Delay
{
public class DelayProfile : ModelBase
{
public bool EnableUsenet { get; set; }
public bool EnableTorrent { get; set; }
public DownloadProtocol PreferredProtocol { get; set; }
public int UsenetDelay { get; set; }
public int TorrentDelay { get; set; }

View File

@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation.Validators;
using NzbDrone.Common;
using NzbDrone.Common.Extensions;
using Omu.ValueInjecter;

View File

@ -26,22 +26,27 @@ define(
if (existing) {
originalAdd.call(this, existing, dontPushVal);
return;
}
var newTag = new TagModel();
newTag.set({ label: item.toLowerCase() });
TagCollection.add(newTag);
else {
var newTag = new TagModel();
newTag.set({ label: item.toLowerCase() });
TagCollection.add(newTag);
newTag.save().done(function () {
item = newTag.toJSON();
originalAdd.call(self, item, dontPushVal);
});
newTag.save().done(function () {
item = newTag.toJSON();
originalAdd.call(self, item, dontPushVal);
});
}
}
else {
originalAdd.call(this, item, dontPushVal);
}
if (this.options.tag) {
self.$input.typeahead('val', '');
}
};
$.fn.tagsinput.Constructor.prototype.remove = function (item, dontPushVal) {
@ -69,6 +74,11 @@ define(
}
});
self.$input.on('focusout', function () {
self.add(self.$input.val());
self.$input.val('');
});
originalBuild.call(this, options);
};

View File

@ -1,29 +1,47 @@
 <div class="col-sm-2">
{{TitleCase preferredProtocol}}
{{#if enableUsenet}}
{{#if enableTorrent}}
{{#if_eq preferredProtocol compare="usenet"}}
Prefer Usenet
{{else}}
Prefer Torrent
{{/if_eq}}
{{else}}
Only Usenet
{{/if}}
{{else}}
Only Torrent
{{/if}}
</div>
<div class="col-sm-2">
{{#if_eq usenetDelay compare="0"}}
No delay
{{else}}
{{#if_eq usenetDelay compare="1"}}
1 minute
{{#if enableUsenet}}
{{#if_eq usenetDelay compare="0"}}
No delay
{{else}}
{{usenetDelay}} minutes
{{#if_eq usenetDelay compare="1"}}
1 minute
{{else}}
{{usenetDelay}} minutes
{{/if_eq}}
{{/if_eq}}
{{/if_eq}}
{{else}}
-
{{/if}}
</div>
<div class="col-sm-2">
{{#if_eq torrentDelay compare="0"}}
No delay
{{else}}
{{#if_eq torrentDelay compare="1"}}
1 minute
{{#if enableTorrent}}
{{#if_eq torrentDelay compare="0"}}
No delay
{{else}}
{{torrentDelay}} minutes
{{#if_eq torrentDelay compare="1"}}
1 minute
{{else}}
{{torrentDelay}} minutes
{{/if_eq}}
{{/if_eq}}
{{/if_eq}}
{{else}}
-
{{/if}}
</div>
<div class="col-sm-5">
{{tagDisplay tags}}

View File

@ -5,7 +5,7 @@
<div class="rule-setting-list">
<div class="rule-setting-header x-header hidden-xs">
<div class="row">
<span class="col-sm-2">Preferred Protocol</span>
<span class="col-sm-2">Protocol</span>
<span class="col-sm-2">Usenet Delay</span>
<span class="col-sm-2">Torrent Delay</span>
<span class="col-sm-5">Tags</span>

View File

@ -18,7 +18,14 @@ define([
_deleteView: DeleteView,
ui: {
tags : '.x-tags'
tags : '.x-tags',
usenetDelay : '.x-usenet-delay',
torrentDelay : '.x-torrent-delay',
protocol : '.x-protocol'
},
events: {
'change .x-protocol' : '_updateModel'
},
initialize: function (options) {
@ -32,11 +39,83 @@ define([
property : 'tags'
});
}
this._toggleControls();
},
_onAfterSave: function () {
this.targetCollection.add(this.model, { merge: true });
vent.trigger(vent.Commands.CloseModalCommand);
},
_updateModel: function () {
var protocol = this.ui.protocol.val();
if (protocol === 'preferUsenet') {
this.model.set({
enableUsenet : true,
enableTorrent : true,
preferredProtocol : 'usenet'
});
}
if (protocol === 'preferTorrent') {
this.model.set({
enableUsenet : true,
enableTorrent : true,
preferredProtocol : 'torrent'
});
}
if (protocol === 'onlyUsenet') {
this.model.set({
enableUsenet : true,
enableTorrent : false,
preferredProtocol : 'usenet'
});
}
if (protocol === 'onlyTorrent') {
this.model.set({
enableUsenet : false,
enableTorrent : true,
preferredProtocol : 'torrent'
});
}
this._toggleControls();
},
_toggleControls: function () {
var enableUsenet = this.model.get('enableUsenet');
var enableTorrent = this.model.get('enableTorrent');
var preferred = this.model.get('preferredProtocol');
if (preferred === 'usenet') {
this.ui.protocol.val('preferUsenet');
}
else {
this.ui.protocol.val('preferTorrent');
}
if (enableUsenet) {
this.ui.usenetDelay.show();
}
else {
this.ui.protocol.val('onlyTorrent');
this.ui.usenetDelay.hide();
}
if (enableTorrent) {
this.ui.torrentDelay.show();
}
else {
this.ui.protocol.val('onlyUsenet');
this.ui.torrentDelay.hide();
}
}
});

View File

@ -10,21 +10,23 @@
<div class="modal-body indexer-modal">
<div class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 control-label">Preferred Protocol</label>
<label class="col-sm-3 control-label">Protocol</label>
<div class="col-sm-1 col-sm-push-5 help-inline">
<i class="icon-nd-form-info" title="Choose which protocol to use when choosing between otherwise equal releases" />
<i class="icon-nd-form-info" title="Choose which protocol(s) to use and which one is preferred when choosing between otherwise equal releases" />
</div>
<div class="col-sm-5 col-sm-pull-1">
<select class="form-control" name="preferredProtocol">
<option value="1">Usenet</option>
<option value="2">Torrents</option>
<select class="form-control x-protocol">
<option value="preferUsenet">Prefer Usenet</option>
<option value="preferTorrent">Prefer Torrent</option>
<option value="onlyUsenet">Only Usenet</option>
<option value="onlyTorrent">Only Torrent</option>
</select>
</div>
</div>
<div class="form-group">
<div class="form-group x-usenet-delay">
<label class="col-sm-3 control-label">Usenet Delay</label>
<div class="col-sm-1 col-sm-push-5 help-inline">
@ -36,7 +38,7 @@
</div>
</div>
<div class="form-group">
<div class="form-group x-torrent-delay">
<label class="col-sm-3 control-label">Torrent Delay</label>
<div class="col-sm-1 col-sm-push-5 help-inline">
@ -49,7 +51,7 @@
</div>
{{#if_eq id compare="1"}}
<div class="alert alert-info" role="alert">The default Delay Profile applies to all series unless overridden by a Delay Profile attached to a matching tag</div>
<div class="alert alert-info" role="alert">This is the default profile. It applies to all series that don't have an explicit profile.</div>
{{else}}
<div class="form-group">
<label class="col-sm-3 control-label">Tags</label>
@ -67,7 +69,9 @@
</div>
<div class="modal-footer">
{{#if id}}
<button class="btn btn-danger pull-left x-delete">delete</button>
{{#if_gt id compare="1"}}
<button class="btn btn-danger pull-left x-delete">delete</button>
{{/if_gt}}
{{/if}}
<span class="indicator x-indicator"><i class="icon-spinner icon-spin"></i></span>
<button class="btn" data-dismiss="modal">cancel</button>