From 77b83b521e40ead9e0eaa136883d596947e47c8d Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 16 Feb 2014 01:56:12 -0800 Subject: [PATCH] Validation, settings UI cleanup and different settings models, oh my New: Download client UI matches other settings Fixed: Prevent drone factory folder from being set to invalid paths/root path for series Fixed: Switching pages in settings will not hide changes Fixed: Test download clients Fixed: Settings are validated before saving --- .../Config/DownloadClientConfigModule.cs | 19 ++++ .../Config/DownloadClientConfigResource.cs | 15 +++ src/NzbDrone.Api/Config/HostConfigModule.cs | 55 +++++++++++ src/NzbDrone.Api/Config/HostConfigResource.cs | 22 +++++ .../Config/IndexerConfigModule.cs | 15 +++ .../Config/IndexerConfigResource.cs | 12 +++ .../Config/MediaManagementConfigModule.cs | 17 ++++ .../Config/MediaManagementConfigResource.cs | 19 ++++ ...{NamingModule.cs => NamingConfigModule.cs} | 4 +- .../Config/NzbDroneConfigModule.cs | 51 ++++++++++ src/NzbDrone.Api/Config/SettingsModule.cs | 71 ------------- src/NzbDrone.Api/NzbDrone.Api.csproj | 13 ++- .../RootFolders/RootFolderModule.cs | 23 +++-- src/NzbDrone.Api/Series/SeriesModule.cs | 1 + .../Validation/RuleBuilderExtensions.cs | 6 +- src/NzbDrone.Common/PathExtensions.cs | 1 - .../SabnzbdTests/SabnzbdFixture.cs | 39 -------- src/NzbDrone.Core.Test/Files/History.txt | 99 ------------------- src/NzbDrone.Core.Test/Files/HistoryEmpty.txt | 50 ---------- src/NzbDrone.Core.Test/Files/JsonError.txt | 4 - .../NzbDrone.Core.Test.csproj | 9 -- .../Configuration/ConfigService.cs | 2 +- .../Configuration/IConfigService.cs | 24 +++-- .../Download/Clients/Blackhole/Blackhole.cs | 1 - .../Download/Clients/FolderSettings.cs | 5 +- .../Clients/Sabnzbd/SabnzbdSettings.cs | 14 ++- src/NzbDrone.Core/NzbDrone.Core.csproj | 5 + .../Validation/FolderValidator.cs} | 6 +- .../Validation/Paths/DroneFactoryValidator.cs | 29 ++++++ .../Validation/Paths/PathExistsValidator.cs | 23 +++++ .../Validation/Paths/PathValidator.cs | 28 ++++++ .../Validation/Paths/RootFolderValidator.cs | 24 +++++ src/UI/.idea/jsLinters/jshint.xml | 4 +- .../DownloadClient/DownloadClientItemView.js | 2 +- .../DownloadClient/DownloadClientLayout.js | 26 ++--- .../DownloadClientLayoutTemplate.html | 18 +--- .../DownloadClientSettingsModel.js | 11 +++ .../Edit/DownloadClientEditView.js | 10 +- .../FailedDownloadHandlingView.js | 37 +++++++ .../FailedDownloadHandlingViewTemplate.html | 65 ++++++++++++ .../Options/DownloadClientOptionsView.js | 26 +++++ .../DownloadClientOptionsViewTemplate.html | 14 +++ .../Settings/General/GeneralSettingsModel.js | 3 +- src/UI/Settings/General/GeneralView.js | 12 ++- ...Template.html => GeneralViewTemplate.html} | 0 .../Settings/Indexers/IndexerSettingsModel.js | 11 +++ .../Indexers/Options/IndexerOptionsView.js | 10 +- .../FileManagement/FileManagementView.js | 27 ++--- .../FileManagementViewTemplate.html | 66 ------------- .../MediaManagement/MediaManagementLayout.js | 2 +- .../MediaManagementSettingsModel.js | 11 +++ .../Permissions/PermissionsView.js | 8 +- .../MediaManagement/Sorting/SortingView.js | 17 ++++ ...Template.html => SortingViewTemplate.html} | 0 .../Settings/MediaManagement/Sorting/View.js | 13 --- src/UI/Settings/SettingsLayout.js | 41 +++++--- src/UI/Settings/SettingsModel.js | 11 --- 57 files changed, 667 insertions(+), 484 deletions(-) create mode 100644 src/NzbDrone.Api/Config/DownloadClientConfigModule.cs create mode 100644 src/NzbDrone.Api/Config/DownloadClientConfigResource.cs create mode 100644 src/NzbDrone.Api/Config/HostConfigModule.cs create mode 100644 src/NzbDrone.Api/Config/HostConfigResource.cs create mode 100644 src/NzbDrone.Api/Config/IndexerConfigModule.cs create mode 100644 src/NzbDrone.Api/Config/IndexerConfigResource.cs create mode 100644 src/NzbDrone.Api/Config/MediaManagementConfigModule.cs create mode 100644 src/NzbDrone.Api/Config/MediaManagementConfigResource.cs rename src/NzbDrone.Api/Config/{NamingModule.cs => NamingConfigModule.cs} (97%) create mode 100644 src/NzbDrone.Api/Config/NzbDroneConfigModule.cs delete mode 100644 src/NzbDrone.Api/Config/SettingsModule.cs delete mode 100644 src/NzbDrone.Core.Test/Files/History.txt delete mode 100644 src/NzbDrone.Core.Test/Files/HistoryEmpty.txt delete mode 100644 src/NzbDrone.Core.Test/Files/JsonError.txt rename src/{NzbDrone.Api/Validation/PathValidator.cs => NzbDrone.Core/Validation/FolderValidator.cs} (74%) create mode 100644 src/NzbDrone.Core/Validation/Paths/DroneFactoryValidator.cs create mode 100644 src/NzbDrone.Core/Validation/Paths/PathExistsValidator.cs create mode 100644 src/NzbDrone.Core/Validation/Paths/PathValidator.cs create mode 100644 src/NzbDrone.Core/Validation/Paths/RootFolderValidator.cs create mode 100644 src/UI/Settings/DownloadClient/DownloadClientSettingsModel.js create mode 100644 src/UI/Settings/DownloadClient/FailedDownloadHandling/FailedDownloadHandlingView.js create mode 100644 src/UI/Settings/DownloadClient/FailedDownloadHandling/FailedDownloadHandlingViewTemplate.html create mode 100644 src/UI/Settings/DownloadClient/Options/DownloadClientOptionsView.js create mode 100644 src/UI/Settings/DownloadClient/Options/DownloadClientOptionsViewTemplate.html rename src/UI/Settings/General/{GeneralTemplate.html => GeneralViewTemplate.html} (100%) create mode 100644 src/UI/Settings/Indexers/IndexerSettingsModel.js create mode 100644 src/UI/Settings/MediaManagement/MediaManagementSettingsModel.js create mode 100644 src/UI/Settings/MediaManagement/Sorting/SortingView.js rename src/UI/Settings/MediaManagement/Sorting/{ViewTemplate.html => SortingViewTemplate.html} (100%) delete mode 100644 src/UI/Settings/MediaManagement/Sorting/View.js delete mode 100644 src/UI/Settings/SettingsModel.js diff --git a/src/NzbDrone.Api/Config/DownloadClientConfigModule.cs b/src/NzbDrone.Api/Config/DownloadClientConfigModule.cs new file mode 100644 index 000000000..16c4dfd3b --- /dev/null +++ b/src/NzbDrone.Api/Config/DownloadClientConfigModule.cs @@ -0,0 +1,19 @@ +using System; +using FluentValidation; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Validation.Paths; + +namespace NzbDrone.Api.Config +{ + public class DownloadClientConfigModule : NzbDroneConfigModule + { + public DownloadClientConfigModule(IConfigService configService, RootFolderValidator rootFolderValidator, PathExistsValidator pathExistsValidator) + : base(configService) + { + SharedValidator.RuleFor(c => c.DownloadedEpisodesFolder) + .SetValidator(rootFolderValidator) + .SetValidator(pathExistsValidator) + .When(c => !String.IsNullOrWhiteSpace(c.DownloadedEpisodesFolder)); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Config/DownloadClientConfigResource.cs b/src/NzbDrone.Api/Config/DownloadClientConfigResource.cs new file mode 100644 index 000000000..f3fa14e8b --- /dev/null +++ b/src/NzbDrone.Api/Config/DownloadClientConfigResource.cs @@ -0,0 +1,15 @@ +using System; +using NzbDrone.Api.REST; + +namespace NzbDrone.Api.Config +{ + public class DownloadClientConfigResource : RestResource + { + public String DownloadedEpisodesFolder { get; set; } + public String DownloadClientWorkingFolders { get; set; } + + public Boolean AutoRedownloadFailed { get; set; } + public Boolean RemoveFailedDownloads { get; set; } + public Boolean EnableFailedDownloadHandling { get; set; } + } +} diff --git a/src/NzbDrone.Api/Config/HostConfigModule.cs b/src/NzbDrone.Api/Config/HostConfigModule.cs new file mode 100644 index 000000000..6d818db6a --- /dev/null +++ b/src/NzbDrone.Api/Config/HostConfigModule.cs @@ -0,0 +1,55 @@ +using System.Linq; +using System.Reflection; +using FluentValidation; +using NzbDrone.Core.Configuration; +using Omu.ValueInjecter; + +namespace NzbDrone.Api.Config +{ + public class HostConfigModule : NzbDroneRestModule + { + private readonly IConfigFileProvider _configFileProvider; + + public HostConfigModule(ConfigFileProvider configFileProvider) + : base("/config/host") + { + _configFileProvider = configFileProvider; + + GetResourceSingle = GetHostConfig; + GetResourceById = GetHostConfig; + UpdateResource = SaveHostConfig; + + SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default"); + SharedValidator.RuleFor(c => c.Port).InclusiveBetween(1, 65535); + + SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationEnabled); + SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationEnabled); + + SharedValidator.RuleFor(c => c.SslPort).InclusiveBetween(1, 65535).When(c => c.EnableSsl); + SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl); + } + + private HostConfigResource GetHostConfig() + { + var resource = new HostConfigResource(); + resource.InjectFrom(_configFileProvider); + resource.Id = 1; + + return resource; + } + + private HostConfigResource GetHostConfig(int id) + { + return GetHostConfig(); + } + + private void SaveHostConfig(HostConfigResource resource) + { + var dictionary = resource.GetType() + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null)); + + _configFileProvider.SaveConfigDictionary(dictionary); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Config/HostConfigResource.cs b/src/NzbDrone.Api/Config/HostConfigResource.cs new file mode 100644 index 000000000..8fc4151d9 --- /dev/null +++ b/src/NzbDrone.Api/Config/HostConfigResource.cs @@ -0,0 +1,22 @@ +using System; +using NzbDrone.Api.REST; + +namespace NzbDrone.Api.Config +{ + public class HostConfigResource : RestResource + { + public Int32 Port { get; set; } + public Int32 SslPort { get; set; } + public Boolean EnableSsl { get; set; } + public Boolean LaunchBrowser { get; set; } + public Boolean AuthenticationEnabled { get; set; } + public String Username { get; set; } + public String Password { get; set; } + public String LogLevel { get; set; } + public String Branch { get; set; } + public String ApiKey { get; set; } + public Boolean Torrent { get; set; } + public String SslCertHash { get; set; } + public String UrlBase { get; set; } + } +} diff --git a/src/NzbDrone.Api/Config/IndexerConfigModule.cs b/src/NzbDrone.Api/Config/IndexerConfigModule.cs new file mode 100644 index 000000000..10df7f2ae --- /dev/null +++ b/src/NzbDrone.Api/Config/IndexerConfigModule.cs @@ -0,0 +1,15 @@ +using FluentValidation; +using NzbDrone.Core.Configuration; + +namespace NzbDrone.Api.Config +{ + public class IndexerConfigModule : NzbDroneConfigModule + { + + public IndexerConfigModule(IConfigService configService) + : base(configService) + { + SharedValidator.RuleFor(c => c.RssSyncInterval).InclusiveBetween(10, 120); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Config/IndexerConfigResource.cs b/src/NzbDrone.Api/Config/IndexerConfigResource.cs new file mode 100644 index 000000000..aeb9706e3 --- /dev/null +++ b/src/NzbDrone.Api/Config/IndexerConfigResource.cs @@ -0,0 +1,12 @@ +using System; +using NzbDrone.Api.REST; + +namespace NzbDrone.Api.Config +{ + 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/Config/MediaManagementConfigModule.cs b/src/NzbDrone.Api/Config/MediaManagementConfigModule.cs new file mode 100644 index 000000000..6c8b8c65e --- /dev/null +++ b/src/NzbDrone.Api/Config/MediaManagementConfigModule.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Validation.Paths; + +namespace NzbDrone.Api.Config +{ + public class MediaManagementConfigModule : NzbDroneConfigModule + { + public MediaManagementConfigModule(IConfigService configService, PathExistsValidator pathExistsValidator) + : base(configService) + { + SharedValidator.RuleFor(c => c.FileChmod).NotEmpty(); + SharedValidator.RuleFor(c => c.FolderChmod).NotEmpty(); + SharedValidator.RuleFor(c => c.RecycleBin).IsValidPath().SetValidator(pathExistsValidator); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs b/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs new file mode 100644 index 000000000..9ff4efc66 --- /dev/null +++ b/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs @@ -0,0 +1,19 @@ +using System; +using NzbDrone.Api.REST; + +namespace NzbDrone.Api.Config +{ + public class MediaManagementConfigResource : RestResource + { + public Boolean AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; } + public String RecycleBin { get; set; } + public Boolean AutoDownloadPropers { get; set; } + public Boolean CreateEmptySeriesFolders { get; set; } + + public Boolean SetPermissionsLinux { get; set; } + public String FileChmod { get; set; } + public String FolderChmod { get; set; } + public String ChownUser { get; set; } + public String ChownGroup { get; set; } + } +} diff --git a/src/NzbDrone.Api/Config/NamingModule.cs b/src/NzbDrone.Api/Config/NamingConfigModule.cs similarity index 97% rename from src/NzbDrone.Api/Config/NamingModule.cs rename to src/NzbDrone.Api/Config/NamingConfigModule.cs index f340f5ab9..eb41ef9e9 100644 --- a/src/NzbDrone.Api/Config/NamingModule.cs +++ b/src/NzbDrone.Api/Config/NamingConfigModule.cs @@ -12,14 +12,14 @@ using Omu.ValueInjecter; namespace NzbDrone.Api.Config { - public class NamingModule : NzbDroneRestModule + public class NamingConfigModule : NzbDroneRestModule { private readonly INamingConfigService _namingConfigService; private readonly IFilenameSampleService _filenameSampleService; private readonly IFilenameValidationService _filenameValidationService; private readonly IBuildFileNames _filenameBuilder; - public NamingModule(INamingConfigService namingConfigService, + public NamingConfigModule(INamingConfigService namingConfigService, IFilenameSampleService filenameSampleService, IFilenameValidationService filenameValidationService, IBuildFileNames filenameBuilder) diff --git a/src/NzbDrone.Api/Config/NzbDroneConfigModule.cs b/src/NzbDrone.Api/Config/NzbDroneConfigModule.cs new file mode 100644 index 000000000..64b31014d --- /dev/null +++ b/src/NzbDrone.Api/Config/NzbDroneConfigModule.cs @@ -0,0 +1,51 @@ +using System.Linq; +using System.Reflection; +using NzbDrone.Api.REST; +using NzbDrone.Core.Configuration; +using Omu.ValueInjecter; + +namespace NzbDrone.Api.Config +{ + public abstract class NzbDroneConfigModule : NzbDroneRestModule where TResource : RestResource, new() + { + private readonly IConfigService _configService; + + protected NzbDroneConfigModule(IConfigService configService) + : this(new TResource().ResourceName.Replace("config", ""), configService) + { + } + + protected NzbDroneConfigModule(string resource, IConfigService configService) : + base("config/" + resource.Trim('/')) + { + _configService = configService; + + GetResourceSingle = GetConfig; + GetResourceById = GetConfig; + UpdateResource = SaveConfig; + } + + private TResource GetConfig() + { + var resource = new TResource(); + resource.InjectFrom(_configService); + resource.Id = 1; + + return resource; + } + + private TResource GetConfig(int id) + { + return GetConfig(); + } + + private void SaveConfig(TResource resource) + { + var dictionary = resource.GetType() + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null)); + + _configService.SaveConfigDictionary(dictionary); + } + } +} diff --git a/src/NzbDrone.Api/Config/SettingsModule.cs b/src/NzbDrone.Api/Config/SettingsModule.cs deleted file mode 100644 index d4135393f..000000000 --- a/src/NzbDrone.Api/Config/SettingsModule.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using Nancy; -using NzbDrone.Api.Extensions; -using NzbDrone.Core.Configuration; - -namespace NzbDrone.Api.Config -{ - public class SettingsModule : NzbDroneApiModule - { - private readonly IConfigService _configService; - private readonly IConfigFileProvider _configFileProvider; - - public SettingsModule(IConfigService configService, IConfigFileProvider configFileProvider) - : base("/settings") - { - _configService = configService; - _configFileProvider = configFileProvider; - Get["/"] = x => GetGeneralSettings(); - Post["/"] = x => SaveGeneralSettings(); - - Get["/host"] = x => GetHostSettings(); - Post["/host"] = x => SaveHostSettings(); - - Get["/log"] = x => GetLogSettings(); - Post["/log"] = x => SaveLogSettings(); - } - - private Response SaveLogSettings() - { - throw new NotImplementedException(); - } - - private Response GetLogSettings() - { - throw new NotImplementedException(); - } - - private Response SaveHostSettings() - { - var request = Request.Body.FromJson>(); - _configFileProvider.SaveConfigDictionary(request); - - return GetHostSettings(); - } - - private Response GetHostSettings() - { - return _configFileProvider.GetConfigDictionary().AsResponse(); - } - - private Response GetGeneralSettings() - { - var collection = Request.Query.Collection; - - if (collection.HasValue && Boolean.Parse(collection.Value)) - return _configService.All().AsResponse(); - - return _configService.AllWithDefaults().AsResponse(); - } - - private Response SaveGeneralSettings() - { - var request = Request.Body.FromJson>(); - _configService.SaveValues(request); - - - return request.AsResponse(); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 04dd817d4..9ca8abe50 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -92,8 +92,17 @@ + + + + + + + + - + + @@ -177,11 +186,9 @@ - - diff --git a/src/NzbDrone.Api/RootFolders/RootFolderModule.cs b/src/NzbDrone.Api/RootFolders/RootFolderModule.cs index 5d0298698..edcf69fe5 100644 --- a/src/NzbDrone.Api/RootFolders/RootFolderModule.cs +++ b/src/NzbDrone.Api/RootFolders/RootFolderModule.cs @@ -5,7 +5,7 @@ using FluentValidation.Results; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.RootFolders; using NzbDrone.Api.Mapping; -using NzbDrone.Api.Validation; +using NzbDrone.Core.Validation.Paths; namespace NzbDrone.Api.RootFolders { @@ -13,7 +13,11 @@ namespace NzbDrone.Api.RootFolders { private readonly IRootFolderService _rootFolderService; - public RootFolderModule(IRootFolderService rootFolderService, ICommandExecutor commandExecutor) + public RootFolderModule(IRootFolderService rootFolderService, + ICommandExecutor commandExecutor, + RootFolderValidator rootFolderValidator, + PathExistsValidator pathExistsValidator, + DroneFactoryValidator droneFactoryValidator) : base(commandExecutor) { _rootFolderService = rootFolderService; @@ -23,7 +27,10 @@ namespace NzbDrone.Api.RootFolders CreateResource = CreateRootFolder; DeleteResource = DeleteFolder; - SharedValidator.RuleFor(c => c.Path).IsValidPath(); + SharedValidator.RuleFor(c => c.Path).IsValidPath() + .SetValidator(rootFolderValidator) + .SetValidator(pathExistsValidator) + .SetValidator(droneFactoryValidator); } private RootFolderResource GetRootFolder(int id) @@ -33,15 +40,7 @@ namespace NzbDrone.Api.RootFolders private int CreateRootFolder(RootFolderResource rootFolderResource) { - try - { - return GetNewId(_rootFolderService.Add, rootFolderResource); - } - catch (Exception ex) - { - throw new ValidationException(new [] { new ValidationFailure("Path", ex.Message) }); - } - + return GetNewId(_rootFolderService.Add, rootFolderResource); } private List GetRootFolders() diff --git a/src/NzbDrone.Api/Series/SeriesModule.cs b/src/NzbDrone.Api/Series/SeriesModule.cs index d87a0f4ec..97255f50c 100644 --- a/src/NzbDrone.Api/Series/SeriesModule.cs +++ b/src/NzbDrone.Api/Series/SeriesModule.cs @@ -12,6 +12,7 @@ using NzbDrone.Core.Tv; using NzbDrone.Api.Validation; using NzbDrone.Api.Mapping; using NzbDrone.Core.Tv.Events; +using NzbDrone.Core.Validation.Paths; namespace NzbDrone.Api.Series { diff --git a/src/NzbDrone.Api/Validation/RuleBuilderExtensions.cs b/src/NzbDrone.Api/Validation/RuleBuilderExtensions.cs index a13cbf52a..42f0d8db0 100644 --- a/src/NzbDrone.Api/Validation/RuleBuilderExtensions.cs +++ b/src/NzbDrone.Api/Validation/RuleBuilderExtensions.cs @@ -1,6 +1,7 @@ using System.Text.RegularExpressions; using FluentValidation; using FluentValidation.Validators; +using NzbDrone.Core.Validation.Paths; namespace NzbDrone.Api.Validation { @@ -21,11 +22,6 @@ namespace NzbDrone.Api.Validation return ruleBuilder.SetValidator(new RegularExpressionValidator("^http(s)?://", RegexOptions.IgnoreCase)).WithMessage("must start with http:// or https://"); } - public static IRuleBuilderOptions IsValidPath(this IRuleBuilder ruleBuilder) - { - return ruleBuilder.SetValidator(new PathValidator()); - } - public static IRuleBuilderOptions NotBlank(this IRuleBuilder ruleBuilder) { return ruleBuilder.SetValidator(new NotNullValidator()).SetValidator(new NotEmptyValidator("")); diff --git a/src/NzbDrone.Common/PathExtensions.cs b/src/NzbDrone.Common/PathExtensions.cs index 309fee9cb..c08770fc8 100644 --- a/src/NzbDrone.Common/PathExtensions.cs +++ b/src/NzbDrone.Common/PathExtensions.cs @@ -71,7 +71,6 @@ namespace NzbDrone.Common return false; } - public static bool ContainsInvalidPathChars(this string text) { return text.IndexOfAny(Path.GetInvalidPathChars()) >= 0; diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs index 8b05eac12..fe9529ef1 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/SabnzbdTests/SabnzbdFixture.cs @@ -49,45 +49,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests }; } - [Test] - public void GetHistory_should_return_a_list_with_items_when_the_history_has_items() - { - Mocker.GetMock() - .Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=history&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass")) - .Returns(ReadAllText("Files", "History.txt")); - - - var result = Subject.GetHistory(); - - - result.Should().HaveCount(1); - } - - [Test] - public void GetHistory_should_return_an_empty_list_when_the_queue_is_empty() - { - Mocker.GetMock() - .Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=history&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass")) - .Returns(ReadAllText("Files", "HistoryEmpty.txt")); - - - var result = Subject.GetHistory(); - - - result.Should().BeEmpty(); - } - - [Test] - public void GetHistory_should_return_an_empty_list_when_there_is_an_error_getting_the_queue() - { - Mocker.GetMock() - .Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=history&output=json&start=0&limit=0&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass")) - .Returns(ReadAllText("Files", "JsonError.txt")); - - - Assert.Throws(() => Subject.GetHistory(), "API Key Incorrect"); - } - [Test] public void downloadNzb_should_use_sabRecentTvPriority_when_recentEpisode_is_true() { diff --git a/src/NzbDrone.Core.Test/Files/History.txt b/src/NzbDrone.Core.Test/Files/History.txt deleted file mode 100644 index 6d13ffe42..000000000 --- a/src/NzbDrone.Core.Test/Files/History.txt +++ /dev/null @@ -1,99 +0,0 @@ -{ - "history":{ - "active_lang":"en", - "paused":false, - "session":"5c770e3197e4fe763423ee7c392c25d1", - "restart_req":false, - "power_options":true, - "slots":[ - { - "action_line":"", - "show_details":"True", - "script_log":"", - "meta":null, - "fail_message":"", - "loaded":false, - "id":9858, - "size":"970 MB", - "category":"tv", - "pp":"D", - "retry":0, - "completeness":0, - "script":"None", - "nzb_name":"The.Mentalist.S04E12.720p.HDTV.x264-IMMERSE.nzb", - "download_time":524, - "storage":"C:\\ServerPool\\ServerFolders\\Unsorted TV\\The Mentalist - 4x12 - My Bloody Valentine [HDTV-720p]", - "status":"Completed", - "script_line":"", - "completed":1327033479, - "nzo_id":"SABnzbd_nzo_0crgis", - "downloaded":1016942445, - "report":"", - "path":"D:\\SABnzbd\\downloading\\The Mentalist - 4x12 - My Bloody Valentine [HDTV-720p]", - "postproc_time":24, - "name":"The Mentalist - 4x12 - My Bloody Valentine [HDTV-720p]", - "url":"", - "bytes":1016942445, - "url_info":"", - "stage_log":[ - { - "name":"Download", - "actions":[ - "Downloaded in 8 minutes 44 seconds at an average of 1.8 MB/s" - ] - }, - { - "name":"Repair", - "actions":[ - "[the.mentalist.s04e12.720p.hdtv.x264-immerse] Quick Check OK" - ] - }, - { - "name":"Unpack", - "actions":[ - "[the.mentalist.s04e12.720p.hdtv.x264-immerse] Unpacked 1 files/folders in 23 seconds" - ] - } - ] - } - ], - "speed":"0 ", - "helpuri":"http://wiki.sabnzbd.org/", - "size":"0 B", - "uptime":"1d", - "total_size":"10.2 T", - "month_size":"445.7 G", - "week_size":"46.6 G", - "version":"0.6.9", - "new_rel_url":"http://sourceforge.net/projects/sabnzbdplus/files/sabnzbdplus/sabnzbd-0.6.14", - "diskspacetotal2":"9314.57", - "color_scheme":"gold", - "diskspacetotal1":"871.41", - "nt":true, - "status":"Idle", - "last_warning":"2012-01-19 23:58:01,736\nWARNING:\nAPI Key incorrect, Use the api key from Config->General in your 3rd party program:", - "have_warnings":"3", - "cache_art":"0", - "sizeleft":"0 B", - "finishaction":null, - "paused_all":false, - "cache_size":"0 B", - "new_release":"0.6.14", - "pause_int":"0", - "mbleft":"0.00", - "diskspace1":"869.82", - "darwin":false, - "timeleft":"0:00:00", - "mb":"0.00", - "noofslots":9724, - "day_size":"0 ", - "eta":"unknown", - "nzb_quota":"", - "loadavg":"", - "cache_max":"-1", - "kbpersec":"0.00", - "speedlimit":"", - "webdir":"D:\\SABnzbd\\SABnzbd\\interfaces\\Plush\\templates", - "diskspace2":"1084.96" - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Files/HistoryEmpty.txt b/src/NzbDrone.Core.Test/Files/HistoryEmpty.txt deleted file mode 100644 index 1c5cb9d95..000000000 --- a/src/NzbDrone.Core.Test/Files/HistoryEmpty.txt +++ /dev/null @@ -1,50 +0,0 @@ -{ - "history":{ - "active_lang":"en", - "paused":false, - "session":"5c770e3197e4fe763423ee7c392c25d1", - "restart_req":false, - "power_options":true, - "slots":[ - - ], - "speed":"0 ", - "helpuri":"http://wiki.sabnzbd.org/", - "size":"0 B", - "uptime":"1d", - "total_size":"10.2 T", - "month_size":"445.7 G", - "week_size":"46.6 G", - "version":"0.6.9", - "new_rel_url":"http://sourceforge.net/projects/sabnzbdplus/files/sabnzbdplus/sabnzbd-0.6.14", - "diskspacetotal2":"9314.57", - "color_scheme":"gold", - "diskspacetotal1":"871.41", - "nt":true, - "status":"Idle", - "last_warning":"2012-01-19 23:58:01,736\nWARNING:\nAPI Key incorrect, Use the api key from Config->General in your 3rd party program:", - "have_warnings":"3", - "cache_art":"0", - "sizeleft":"0 B", - "finishaction":null, - "paused_all":false, - "cache_size":"0 B", - "new_release":"0.6.14", - "pause_int":"0", - "mbleft":"0.00", - "diskspace1":"869.82", - "darwin":false, - "timeleft":"0:00:00", - "mb":"0.00", - "noofslots":9724, - "day_size":"0 ", - "eta":"unknown", - "nzb_quota":"", - "loadavg":"", - "cache_max":"-1", - "kbpersec":"0.00", - "speedlimit":"", - "webdir":"D:\\SABnzbd\\SABnzbd\\interfaces\\Plush\\templates", - "diskspace2":"1084.96" - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Files/JsonError.txt b/src/NzbDrone.Core.Test/Files/JsonError.txt deleted file mode 100644 index aa32a0bdd..000000000 --- a/src/NzbDrone.Core.Test/Files/JsonError.txt +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": false, - "error": "API Key Incorrect" -} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index f50f25adf..3ee136d83 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -287,18 +287,9 @@ Always - - Always - Always - - Always - - - Always - App.config diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 6e197460e..a0aa00bc8 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -54,7 +54,7 @@ namespace NzbDrone.Core.Configuration return dict; } - public void SaveValues(Dictionary configValues) + public void SaveConfigDictionary(Dictionary configValues) { var allWithDefaults = AllWithDefaults(); diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index a9d121e2f..ca44cc046 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -7,23 +7,33 @@ namespace NzbDrone.Core.Configuration { IEnumerable All(); Dictionary AllWithDefaults(); + void SaveConfigDictionary(Dictionary configValues); + + //Download Client String DownloadedEpisodesFolder { get; set; } - bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; } - int Retention { get; set; } - string RecycleBin { get; set; } - string ReleaseRestrictions { get; set; } - Int32 RssSyncInterval { get; set; } - Boolean AutoDownloadPropers { get; set; } String DownloadClientWorkingFolders { get; set; } + + //Failed Download Handling (Download client) Boolean AutoRedownloadFailed { get; set; } Boolean RemoveFailedDownloads { get; set; } Boolean EnableFailedDownloadHandling { get; set; } + + //Media Management + Boolean AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; } + String RecycleBin { get; set; } + Boolean AutoDownloadPropers { get; set; } Boolean CreateEmptySeriesFolders { get; set; } - void SaveValues(Dictionary configValues); + + //Permissions (Media Management) Boolean SetPermissionsLinux { get; set; } String FileChmod { get; set; } String FolderChmod { get; set; } String ChownUser { get; set; } String ChownGroup { get; set; } + + //Indexers + Int32 Retention { get; set; } + Int32 RssSyncInterval { get; set; } + String ReleaseRestrictions { get; set; } } } diff --git a/src/NzbDrone.Core/Download/Clients/Blackhole/Blackhole.cs b/src/NzbDrone.Core/Download/Clients/Blackhole/Blackhole.cs index 4cfc0cf2d..28cc9eb9e 100644 --- a/src/NzbDrone.Core/Download/Clients/Blackhole/Blackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/Blackhole/Blackhole.cs @@ -4,7 +4,6 @@ using System.IO; using NLog; using NzbDrone.Common; using NzbDrone.Common.Disk; -using NzbDrone.Core.Configuration; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; diff --git a/src/NzbDrone.Core/Download/Clients/FolderSettings.cs b/src/NzbDrone.Core/Download/Clients/FolderSettings.cs index f11169203..cacb847ea 100644 --- a/src/NzbDrone.Core/Download/Clients/FolderSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/FolderSettings.cs @@ -1,8 +1,10 @@ using System; using FluentValidation; using FluentValidation.Results; +using NzbDrone.Common.Disk; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation.Paths; namespace NzbDrone.Core.Download.Clients { @@ -10,7 +12,8 @@ namespace NzbDrone.Core.Download.Clients { public FolderSettingsValidator() { - RuleFor(c => c.Folder).NotEmpty(); + //Todo: Validate that the path actually exists + RuleFor(c => c.Folder).IsValidPath(); } } diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs index a6373b379..de5e87fb8 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs @@ -2,7 +2,6 @@ using FluentValidation; using FluentValidation.Results; using NzbDrone.Core.Annotations; -using NzbDrone.Core.Download.Clients.Nzbget; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Download.Clients.Sabnzbd @@ -14,7 +13,18 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd RuleFor(c => c.Host).NotEmpty(); RuleFor(c => c.Port).GreaterThan(0); - //Todo: either API key or Username/Password needs to be valid + RuleFor(c => c.ApiKey).NotEmpty() + .WithMessage("API Key is required when username/password are not configured") + .When(c => String.IsNullOrWhiteSpace(c.Username)); + + RuleFor(c => c.Username).NotEmpty() + .WithMessage("Username is required when API key is not configured") + .When(c => String.IsNullOrWhiteSpace(c.ApiKey)); + + + RuleFor(c => c.Password).NotEmpty() + .WithMessage("Password is required when API key is not configured") + .When(c => String.IsNullOrWhiteSpace(c.ApiKey)); } } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 4391b60eb..efa52abe7 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -660,6 +660,11 @@ + + + + + diff --git a/src/NzbDrone.Api/Validation/PathValidator.cs b/src/NzbDrone.Core/Validation/FolderValidator.cs similarity index 74% rename from src/NzbDrone.Api/Validation/PathValidator.cs rename to src/NzbDrone.Core/Validation/FolderValidator.cs index f7cf37eab..daa3645bf 100644 --- a/src/NzbDrone.Api/Validation/PathValidator.cs +++ b/src/NzbDrone.Core/Validation/FolderValidator.cs @@ -1,11 +1,11 @@ using FluentValidation.Validators; using NzbDrone.Common; -namespace NzbDrone.Api.Validation +namespace NzbDrone.Core.Validation { - public class PathValidator : PropertyValidator + public class FolderValidator : PropertyValidator { - public PathValidator() + public FolderValidator() : base("Invalid Path") { } diff --git a/src/NzbDrone.Core/Validation/Paths/DroneFactoryValidator.cs b/src/NzbDrone.Core/Validation/Paths/DroneFactoryValidator.cs new file mode 100644 index 000000000..ec5447774 --- /dev/null +++ b/src/NzbDrone.Core/Validation/Paths/DroneFactoryValidator.cs @@ -0,0 +1,29 @@ +using System; +using FluentValidation.Validators; +using NzbDrone.Common; +using NzbDrone.Core.Configuration; + +namespace NzbDrone.Core.Validation.Paths +{ + public class DroneFactoryValidator : PropertyValidator + { + private readonly IConfigService _configService; + + public DroneFactoryValidator(IConfigService configService) + : base("Path is already used for drone factory") + { + _configService = configService; + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) return false; + + var droneFactory = _configService.DownloadedEpisodesFolder; + + if (String.IsNullOrWhiteSpace(droneFactory)) return true; + + return !droneFactory.PathEquals(context.PropertyValue.ToString()); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Validation/Paths/PathExistsValidator.cs b/src/NzbDrone.Core/Validation/Paths/PathExistsValidator.cs new file mode 100644 index 000000000..8e3e39aed --- /dev/null +++ b/src/NzbDrone.Core/Validation/Paths/PathExistsValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation.Validators; +using NzbDrone.Common.Disk; + +namespace NzbDrone.Core.Validation.Paths +{ + public class PathExistsValidator : PropertyValidator + { + private readonly IDiskProvider _diskProvider; + + public PathExistsValidator(IDiskProvider diskProvider) + : base("Path does not exist") + { + _diskProvider = diskProvider; + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) return false; + + return (_diskProvider.FolderExists(context.PropertyValue.ToString())); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Validation/Paths/PathValidator.cs b/src/NzbDrone.Core/Validation/Paths/PathValidator.cs new file mode 100644 index 000000000..a77c76ae5 --- /dev/null +++ b/src/NzbDrone.Core/Validation/Paths/PathValidator.cs @@ -0,0 +1,28 @@ +using FluentValidation; +using FluentValidation.Validators; +using NzbDrone.Common; + +namespace NzbDrone.Core.Validation.Paths +{ + public static class PathValidation + { + public static IRuleBuilderOptions IsValidPath(this IRuleBuilder ruleBuilder) + { + return ruleBuilder.SetValidator(new PathValidator()); + } + } + + public class PathValidator : PropertyValidator + { + public PathValidator() + : base("Invalid Path") + { + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) return false; + return context.PropertyValue.ToString().IsPathValid(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Validation/Paths/RootFolderValidator.cs b/src/NzbDrone.Core/Validation/Paths/RootFolderValidator.cs new file mode 100644 index 000000000..382056e24 --- /dev/null +++ b/src/NzbDrone.Core/Validation/Paths/RootFolderValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation.Validators; +using NzbDrone.Common; +using NzbDrone.Core.RootFolders; + +namespace NzbDrone.Core.Validation.Paths +{ + public class RootFolderValidator : PropertyValidator + { + private readonly IRootFolderService _rootFolderService; + + public RootFolderValidator(IRootFolderService rootFolderService) + : base("Path is already configured as a root folder") + { + _rootFolderService = rootFolderService; + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) return true; + + return (!_rootFolderService.All().Exists(r => r.Path.PathEquals(context.PropertyValue.ToString()))); + } + } +} \ No newline at end of file diff --git a/src/UI/.idea/jsLinters/jshint.xml b/src/UI/.idea/jsLinters/jshint.xml index 4e0df49ad..e85398a55 100644 --- a/src/UI/.idea/jsLinters/jshint.xml +++ b/src/UI/.idea/jsLinters/jshint.xml @@ -8,16 +8,16 @@