diff --git a/src/NzbDrone.Api/Config/HostConfigModule.cs b/src/NzbDrone.Api/Config/HostConfigModule.cs index 37821bfbe..9326cf0db 100644 --- a/src/NzbDrone.Api/Config/HostConfigModule.cs +++ b/src/NzbDrone.Api/Config/HostConfigModule.cs @@ -27,24 +27,23 @@ namespace NzbDrone.Api.Config GetResourceById = GetHostConfig; UpdateResource = SaveHostConfig; - SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default"); + SharedValidator.RuleFor(c => c.BindAddress) + .ValidIp4Address() + .NotListenAllIp4Address() + .When(c => c.BindAddress != "*"); + SharedValidator.RuleFor(c => c.Port).ValidPort(); + SharedValidator.RuleFor(c => c.UrlBase).ValidUrlBase(); + SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None); SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None); SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl); SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows); + SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default"); SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script); - - SharedValidator.RuleFor(c => c.BindAddress) - .ValidIp4Address() - .NotListenAllIp4Address() - .When(c => c.BindAddress != "*"); - - SharedValidator.RuleFor(c => c.UrlBase) - .ValidUrlBase(); } private HostConfigResource GetHostConfig() diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/074_disable_eztv.cs b/src/NzbDrone.Core.Test/Datastore/Migration/074_disable_eztvFixture.cs similarity index 95% rename from src/NzbDrone.Core.Test/Datastore/Migration/074_disable_eztv.cs rename to src/NzbDrone.Core.Test/Datastore/Migration/074_disable_eztvFixture.cs index 455bba398..7e27249ec 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/074_disable_eztv.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/074_disable_eztvFixture.cs @@ -9,7 +9,7 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.Datastore.Migration { [TestFixture] - public class disable_eztv : MigrationTest + public class disable_eztvFixture : MigrationTest { [Test] public void should_disable_rss_for_eztv() diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/079_dedupe_tagsFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/079_dedupe_tagsFixture.cs index 6ce90cdc3..c7f01bf57 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/079_dedupe_tagsFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/079_dedupe_tagsFixture.cs @@ -10,7 +10,7 @@ using NzbDrone.Core.Tv; namespace NzbDrone.Core.Test.Datastore.Migration { [TestFixture] - public class dedupe_tags : MigrationTest + public class dedupe_tagsFixture : MigrationTest { [Test] public void should_not_fail_if_series_tags_are_null() diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/081_move_dot_prefix_to_transmission_categoryFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/081_move_dot_prefix_to_transmission_categoryFixture.cs index 841239e55..372984568 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/081_move_dot_prefix_to_transmission_categoryFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/081_move_dot_prefix_to_transmission_categoryFixture.cs @@ -11,7 +11,7 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.Datastore.Migration { [TestFixture] - public class move_dot_prefix_to_transmission_category : MigrationTest + public class move_dot_prefix_to_transmission_categoryFixture : MigrationTest { [Test] public void should_not_fail_if_no_transmission() diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/084_update_quality_minmax_sizeFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/084_update_quality_minmax_sizeFixture.cs index a5f628245..556bb60dd 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/084_update_quality_minmax_sizeFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/084_update_quality_minmax_sizeFixture.cs @@ -8,7 +8,7 @@ using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.Datastore.Migration { [TestFixture] - public class update_quality_minmax_size : MigrationTest + public class update_quality_minmax_sizeFixture : MigrationTest { [Test] public void should_not_fail_if_empty() diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/085_expand_transmission_urlbaseFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/085_expand_transmission_urlbaseFixture.cs new file mode 100644 index 000000000..cd43a1907 --- /dev/null +++ b/src/NzbDrone.Core.Test/Datastore/Migration/085_expand_transmission_urlbaseFixture.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Download; +using NzbDrone.Core.Download.Clients.Deluge; +using NzbDrone.Core.Download.Clients.Sabnzbd; +using NzbDrone.Core.Download.Clients.Transmission; +using NzbDrone.Core.Test.Framework; +using System.Drawing; +namespace NzbDrone.Core.Test.Datastore.Migration +{ + [TestFixture] + public class expand_transmission_urlbaseFixture : MigrationTest + { + [Test] + public void should_not_fail_if_no_transmission() + { + WithTestDb(c => + { + c.Insert.IntoTable("DownloadClients").Row(new + { + Enable = 1, + Name = "Deluge", + Implementation = "Deluge", + Settings = new DelugeSettings + { + Host = "127.0.0.1", + TvCategory = "abc", + UrlBase = "/my/" + }.ToJson(), + ConfigContract = "DelugeSettings" + }); + }); + + var items = Mocker.Resolve().All(); + + items.Should().HaveCount(1); + + items.First().Settings.As().UrlBase.Should().Be("/my/"); + } + + [Test] + public void should_be_updated_for_transmission() + { + WithTestDb(c => + { + c.Insert.IntoTable("DownloadClients").Row(new + { + Enable = 1, + Name = "Trans", + Implementation = "Transmission", + Settings = new TransmissionSettings + { + Host = "127.0.0.1", + TvCategory = "abc", + UrlBase = null + }.ToJson(), + ConfigContract = "TransmissionSettings" + }); + }); + + var items = Mocker.Resolve().All(); + + items.Should().HaveCount(1); + + items.First().Settings.As().UrlBase.Should().Be("/transmission/"); + } + + [Test] + public void should_be_append_to_existing_urlbase() + { + WithTestDb(c => + { + c.Insert.IntoTable("DownloadClients").Row(new + { + Enable = 1, + Name = "Trans", + Implementation = "Transmission", + Settings = new TransmissionSettings + { + Host = "127.0.0.1", + TvCategory = "abc", + UrlBase = "/my/url/" + }.ToJson(), + ConfigContract = "TransmissionSettings" + }); + }); + + var items = Mocker.Resolve().All(); + + items.Should().HaveCount(1); + + items.First().Settings.As().UrlBase.Should().Be("/my/url/transmission/"); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index a9e921fb2..236181bb6 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -118,9 +118,10 @@ - + + @@ -515,4 +516,4 @@ --> - + \ No newline at end of file diff --git a/src/NzbDrone.Core/Datastore/Migration/085_expand_transmission_urlbase.cs b/src/NzbDrone.Core/Datastore/Migration/085_expand_transmission_urlbase.cs new file mode 100644 index 000000000..56df0f514 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/085_expand_transmission_urlbase.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using FluentMigrator; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(85)] + public class expand_transmission_urlbase : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Execute.WithConnection(UpdateTransmissionSettings); + } + + private void UpdateTransmissionSettings(IDbConnection conn, IDbTransaction tran) + { + using (var cmd = conn.CreateCommand()) + { + cmd.Transaction = tran; + cmd.CommandText = "SELECT Id, Settings FROM DownloadClients WHERE Implementation = 'Transmission'"; + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var id = reader.GetInt32(0); + var settingsJson = reader.GetString(1); + + var settings = Json.Deserialize>(settingsJson); + + var urlBase = settings.GetValueOrDefault("urlBase", "") as string; + + if (urlBase.IsNullOrWhiteSpace()) + { + settings["urlBase"] = "/transmission/"; + } + else + { + settings["urlBase"] = string.Format("/{0}/transmission/", urlBase.Trim('/')); + } + + using (var updateCmd = conn.CreateCommand()) + { + updateCmd.Transaction = tran; + updateCmd.CommandText = "UPDATE DownloadClients SET Settings = ? WHERE Id = ?"; + updateCmd.AddParameter(settings.ToJson()); + updateCmd.AddParameter(id); + + updateCmd.ExecuteNonQuery(); + } + } + } + } + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs index 777d433bf..c0c30195d 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionProxy.cs @@ -260,15 +260,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission { var protocol = settings.UseSsl ? "https" : "http"; - String url; - if (!settings.UrlBase.IsNullOrWhiteSpace()) - { - url = String.Format(@"{0}://{1}:{2}/{3}/transmission/rpc", protocol, settings.Host, settings.Port, settings.UrlBase.Trim('/')); - } - else - { - url = String.Format(@"{0}://{1}:{2}/transmission/rpc", protocol, settings.Host, settings.Port); - } + var url = String.Format(@"{0}://{1}:{2}/{3}/rpc", protocol, settings.Host, settings.Port, settings.UrlBase.Trim('/')); var restClient = RestClientFactory.BuildClient(url); restClient.FollowRedirects = false; diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs index 19d9dcc1b..15756e01c 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs @@ -13,6 +13,8 @@ namespace NzbDrone.Core.Download.Clients.Transmission RuleFor(c => c.Host).ValidHost(); RuleFor(c => c.Port).GreaterThan(0); + RuleFor(c => c.UrlBase).ValidUrlBase(); + RuleFor(c => c.TvCategory).Matches(@"^\.?[-a-z]*$").WithMessage("Allowed characters a-z and -"); } } @@ -25,6 +27,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission { Host = "localhost"; Port = 9091; + UrlBase = "/transmission/"; } [FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)] @@ -33,7 +36,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission [FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)] public Int32 Port { get; set; } - [FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the transmission rpc url, see http://[host]:[port]/[urlBase]/transmission/rpc")] + [FieldDefinition(2, Label = "Url Base", Type = FieldType.Textbox, Advanced = true, HelpText = "Adds a prefix to the transmission rpc url, eg http://[host]:[port]/[urlBase]/rpc, defaults to '/transmission/'")] public String UrlBase { get; set; } [FieldDefinition(3, Label = "Username", Type = FieldType.Textbox)] diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 2f1bac1f2..ce2f7577c 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -253,6 +253,7 @@ + diff --git a/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs b/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs index d8207ee76..231a7fbeb 100644 --- a/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs +++ b/src/NzbDrone.Core/Validation/RuleBuilderExtensions.cs @@ -34,6 +34,11 @@ namespace NzbDrone.Core.Validation return ruleBuilder.SetValidator(new RegularExpressionValidator("^https?://[-a-z0-9.]+", RegexOptions.IgnoreCase)).WithMessage("must be valid URL that starts with http(s)://"); } + public static IRuleBuilderOptions ValidUrlBase(this IRuleBuilder ruleBuilder) + { + return ruleBuilder.SetValidator(new RegularExpressionValidator(@"^(?!\/?https?://[-a-z0-9.]+)", RegexOptions.IgnoreCase)).WithMessage("Must be a valid URL path (ie: '/sonarr')"); + } + public static IRuleBuilderOptions ValidPort(this IRuleBuilder ruleBuilder) { return ruleBuilder.SetValidator(new InclusiveBetweenValidator(1, 65535)) @@ -62,10 +67,5 @@ namespace NzbDrone.Core.Validation { return ruleBuilder.WithState(v => NzbDroneValidationState.Warning); } - - public static IRuleBuilderOptions ValidUrlBase(this IRuleBuilder ruleBuilder) - { - return ruleBuilder.SetValidator(new RegularExpressionValidator(@"^(?!\/?https?://[-a-z0-9.]+)", RegexOptions.IgnoreCase)).WithMessage("Must be a valid URL path (ie: '/sonarr')"); - } } } \ No newline at end of file