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
This commit is contained in:
parent
606d78f5e1
commit
77b83b521e
|
@ -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<DownloadClientConfigResource>
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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<HostConfigResource>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using FluentValidation;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Api.Config
|
||||
{
|
||||
public class IndexerConfigModule : NzbDroneConfigModule<IndexerConfigResource>
|
||||
{
|
||||
|
||||
public IndexerConfigModule(IConfigService configService)
|
||||
: base(configService)
|
||||
{
|
||||
SharedValidator.RuleFor(c => c.RssSyncInterval).InclusiveBetween(10, 120);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using FluentValidation;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
|
||||
namespace NzbDrone.Api.Config
|
||||
{
|
||||
public class MediaManagementConfigModule : NzbDroneConfigModule<MediaManagementConfigResource>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -12,14 +12,14 @@ using Omu.ValueInjecter;
|
|||
|
||||
namespace NzbDrone.Api.Config
|
||||
{
|
||||
public class NamingModule : NzbDroneRestModule<NamingConfigResource>
|
||||
public class NamingConfigModule : NzbDroneRestModule<NamingConfigResource>
|
||||
{
|
||||
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)
|
|
@ -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<TResource> : NzbDroneRestModule<TResource> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Dictionary<string, object>>();
|
||||
_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<Dictionary<string, object>>();
|
||||
_configService.SaveValues(request);
|
||||
|
||||
|
||||
return request.AsResponse();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -92,8 +92,17 @@
|
|||
<Compile Include="ClientSchema\SelectOption.cs" />
|
||||
<Compile Include="Commands\CommandModule.cs" />
|
||||
<Compile Include="Commands\CommandResource.cs" />
|
||||
<Compile Include="Config\IndexerConfigModule.cs" />
|
||||
<Compile Include="Config\MediaManagementConfigModule.cs" />
|
||||
<Compile Include="Config\MediaManagementConfigResource.cs" />
|
||||
<Compile Include="Config\NzbDroneConfigModule.cs" />
|
||||
<Compile Include="Config\DownloadClientConfigModule.cs" />
|
||||
<Compile Include="Config\DownloadClientConfigResource.cs" />
|
||||
<Compile Include="Config\HostConfigModule.cs" />
|
||||
<Compile Include="Config\HostConfigResource.cs" />
|
||||
<Compile Include="Config\NamingConfigResource.cs" />
|
||||
<Compile Include="Config\NamingModule.cs" />
|
||||
<Compile Include="Config\NamingConfigModule.cs" />
|
||||
<Compile Include="Config\IndexerConfigResource.cs" />
|
||||
<Compile Include="DownloadClient\DownloadClientModule.cs" />
|
||||
<Compile Include="DownloadClient\DownloadClientResource.cs" />
|
||||
<Compile Include="DiskSpace\DiskSpaceModule.cs" />
|
||||
|
@ -177,11 +186,9 @@
|
|||
<Compile Include="Qualities\QualityDefinitionResource.cs" />
|
||||
<Compile Include="Qualities\QualityDefinitionModule.cs" />
|
||||
<Compile Include="Extensions\ReqResExtensions.cs" />
|
||||
<Compile Include="Config\SettingsModule.cs" />
|
||||
<Compile Include="System\SystemModule.cs" />
|
||||
<Compile Include="TinyIoCNancyBootstrapper.cs" />
|
||||
<Compile Include="Update\UpdateModule.cs" />
|
||||
<Compile Include="Validation\PathValidator.cs" />
|
||||
<Compile Include="Validation\RuleBuilderExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -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)
|
||||
|
@ -32,17 +39,9 @@ namespace NzbDrone.Api.RootFolders
|
|||
}
|
||||
|
||||
private int CreateRootFolder(RootFolderResource rootFolderResource)
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetNewId<RootFolder>(_rootFolderService.Add, rootFolderResource);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ValidationException(new [] { new ValidationFailure("Path", ex.Message) });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private List<RootFolderResource> GetRootFolders()
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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<T, string> IsValidPath<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||
{
|
||||
return ruleBuilder.SetValidator(new PathValidator());
|
||||
}
|
||||
|
||||
public static IRuleBuilderOptions<T, string> NotBlank<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||
{
|
||||
return ruleBuilder.SetValidator(new NotNullValidator()).SetValidator(new NotEmptyValidator(""));
|
||||
|
|
|
@ -71,7 +71,6 @@ namespace NzbDrone.Common
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static bool ContainsInvalidPathChars(this string text)
|
||||
{
|
||||
return text.IndexOfAny(Path.GetInvalidPathChars()) >= 0;
|
||||
|
|
|
@ -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<IHttpProvider>()
|
||||
.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<IHttpProvider>()
|
||||
.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<IHttpProvider>()
|
||||
.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<ApplicationException>(() => Subject.GetHistory(), "API Key Incorrect");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void downloadNzb_should_use_sabRecentTvPriority_when_recentEpisode_is_true()
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"status": false,
|
||||
"error": "API Key Incorrect"
|
||||
}
|
|
@ -287,18 +287,9 @@
|
|||
<Content Include="Files\LongOverview.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Files\HistoryEmpty.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Files\Queue.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Files\History.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Files\JsonError.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Include="..\NzbDrone.Test.Common\App.config">
|
||||
<Link>App.config</Link>
|
||||
</None>
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace NzbDrone.Core.Configuration
|
|||
return dict;
|
||||
}
|
||||
|
||||
public void SaveValues(Dictionary<string, object> configValues)
|
||||
public void SaveConfigDictionary(Dictionary<string, object> configValues)
|
||||
{
|
||||
var allWithDefaults = AllWithDefaults();
|
||||
|
||||
|
|
|
@ -7,23 +7,33 @@ namespace NzbDrone.Core.Configuration
|
|||
{
|
||||
IEnumerable<Config> All();
|
||||
Dictionary<String, Object> AllWithDefaults();
|
||||
void SaveConfigDictionary(Dictionary<string, object> 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<string, object> 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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -660,6 +660,11 @@
|
|||
<Compile Include="Update\UpdatePackageProvider.cs" />
|
||||
<Compile Include="Update\UpdatePackage.cs" />
|
||||
<Compile Include="Update\UpdateCheckService.cs" />
|
||||
<Compile Include="Validation\Paths\DroneFactoryValidator.cs" />
|
||||
<Compile Include="Validation\Paths\PathValidator.cs" />
|
||||
<Compile Include="Validation\Paths\RootFolderValidator.cs" />
|
||||
<Compile Include="Validation\Paths\PathExistsValidator.cs" />
|
||||
<Compile Include="Validation\FolderValidator.cs" />
|
||||
<Compile Include="Validation\RuleBuilderExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -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")
|
||||
{
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using FluentValidation;
|
||||
using FluentValidation.Validators;
|
||||
using NzbDrone.Common;
|
||||
|
||||
namespace NzbDrone.Core.Validation.Paths
|
||||
{
|
||||
public static class PathValidation
|
||||
{
|
||||
public static IRuleBuilderOptions<T, string> IsValidPath<T>(this IRuleBuilder<T, string> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,16 +8,16 @@
|
|||
<option es3="false" />
|
||||
<option forin="true" />
|
||||
<option immed="true" />
|
||||
<option latedef="true" />
|
||||
<option newcap="true" />
|
||||
<option noarg="true" />
|
||||
<option noempty="false" />
|
||||
<option nonew="true" />
|
||||
<option plusplus="false" />
|
||||
<option undef="true" />
|
||||
<option unused="true" />
|
||||
<option strict="true" />
|
||||
<option trailing="false" />
|
||||
<option latedef="true" />
|
||||
<option unused="true" />
|
||||
<option quotmark="single" />
|
||||
<option maxdepth="3" />
|
||||
<option asi="false" />
|
||||
|
|
|
@ -22,7 +22,7 @@ define(
|
|||
},
|
||||
|
||||
_edit: function () {
|
||||
var view = new EditView({ model: this.model});
|
||||
var view = new EditView({ model: this.model, downloadClientCollection: this.model.collection });
|
||||
AppLayout.modalRegion.show(view);
|
||||
},
|
||||
|
||||
|
|
|
@ -5,24 +5,17 @@ define(
|
|||
'marionette',
|
||||
'Settings/DownloadClient/DownloadClientCollection',
|
||||
'Settings/DownloadClient/DownloadClientCollectionView',
|
||||
'Mixins/AsModelBoundView',
|
||||
'Mixins/AutoComplete',
|
||||
'bootstrap'
|
||||
], function (Marionette, DownloadClientCollection, DownloadClientCollectionView, AsModelBoundView) {
|
||||
'Settings/DownloadClient/Options/DownloadClientOptionsView',
|
||||
'Settings/DownloadClient/FailedDownloadHandling/FailedDownloadHandlingView'
|
||||
], function (Marionette, DownloadClientCollection, DownloadClientCollectionView, DownloadClientOptionsView, FailedDownloadHandlingView) {
|
||||
|
||||
var view = Marionette.Layout.extend({
|
||||
return Marionette.Layout.extend({
|
||||
template : 'Settings/DownloadClient/DownloadClientLayoutTemplate',
|
||||
|
||||
regions: {
|
||||
downloadClients: '#x-download-clients-region'
|
||||
},
|
||||
|
||||
ui: {
|
||||
droneFactory: '.x-path'
|
||||
},
|
||||
|
||||
events: {
|
||||
'change .x-download-client': 'downloadClientChanged'
|
||||
downloadClients : '#x-download-clients-region',
|
||||
downloadClientOptions : '#x-download-client-options-region',
|
||||
failedDownloadHandling : '#x-failed-download-handling-region'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
|
@ -32,9 +25,8 @@ define(
|
|||
|
||||
onShow: function () {
|
||||
this.downloadClients.show(new DownloadClientCollectionView({ collection: this.downloadClientCollection }));
|
||||
this.ui.droneFactory.autoComplete('/directories');
|
||||
this.downloadClientOptions.show(new DownloadClientOptionsView({ model: this.model }));
|
||||
this.failedDownloadHandling.show(new FailedDownloadHandlingView({ model: this.model }));
|
||||
}
|
||||
});
|
||||
|
||||
return AsModelBoundView.call(view);
|
||||
});
|
|
@ -1,16 +1,6 @@
|
|||
<div id="x-download-clients-region"></div>
|
||||
<div class="form-horizontal">
|
||||
<div id="x-download-client-options-region"></div>
|
||||
<div id="x-failed-download-handling-region"></div>
|
||||
</div>
|
||||
|
||||
<fieldset class="form-horizontal">
|
||||
<legend>Options</legend>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Drone Factory</label>
|
||||
|
||||
<div class="controls">
|
||||
<input type="text" name="downloadedEpisodesFolder" class="x-path"/>
|
||||
<span class="help-inline">
|
||||
<i class="icon-nd-form-info" title="The folder where your download client downloads TV shows to (Completed Download Directory)"/>
|
||||
<i class="icon-nd-form-warning" title="Do not use the folder that contains some or all of your sorted and named TV shows - doing so could cause data loss"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
define(
|
||||
[
|
||||
'Settings/SettingsModelBase'
|
||||
], function (SettingsModelBase) {
|
||||
return SettingsModelBase.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/config/downloadclient',
|
||||
successMessage: 'Download client settings saved',
|
||||
errorMessage : 'Failed to save download client settings'
|
||||
});
|
||||
});
|
|
@ -8,13 +8,14 @@ define(
|
|||
'Settings/DownloadClient/Delete/DownloadClientDeleteView',
|
||||
'Commands/CommandController',
|
||||
'Mixins/AsModelBoundView',
|
||||
'Mixins/AsValidatedView',
|
||||
'underscore',
|
||||
'Form/FormBuilder',
|
||||
'Mixins/AutoComplete',
|
||||
'bootstrap'
|
||||
], function (vent, AppLayout, Marionette, DeleteView, CommandController, AsModelBoundView, _) {
|
||||
], function (vent, AppLayout, Marionette, DeleteView, CommandController, AsModelBoundView, AsValidatedView, _) {
|
||||
|
||||
var model = Marionette.ItemView.extend({
|
||||
var view = Marionette.ItemView.extend({
|
||||
template: 'Settings/DownloadClient/Edit/DownloadClientEditViewTemplate',
|
||||
|
||||
ui: {
|
||||
|
@ -89,5 +90,8 @@ define(
|
|||
}
|
||||
});
|
||||
|
||||
return AsModelBoundView.call(model);
|
||||
AsModelBoundView.call(view);
|
||||
AsValidatedView.call(view);
|
||||
|
||||
return view;
|
||||
});
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
'use strict';
|
||||
define(
|
||||
[
|
||||
'marionette',
|
||||
'Mixins/AsModelBoundView',
|
||||
'Mixins/AsValidatedView'
|
||||
], function (Marionette, AsModelBoundView, AsValidatedView) {
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
template: 'Settings/DownloadClient/FailedDownloadHandling/FailedDownloadHandlingViewTemplate',
|
||||
|
||||
ui: {
|
||||
failedDownloadHandlingCheckbox: '.x-failed-download-handling',
|
||||
failedDownloadOptions : '.x-failed-download-options'
|
||||
},
|
||||
|
||||
events: {
|
||||
'change .x-failed-download-handling': '_setFailedDownloadOptionsVisibility'
|
||||
},
|
||||
|
||||
_setFailedDownloadOptionsVisibility: function () {
|
||||
var checked = this.ui.failedDownloadHandlingCheckbox.prop('checked');
|
||||
if (checked) {
|
||||
this.ui.failedDownloadOptions.slideDown();
|
||||
}
|
||||
|
||||
else {
|
||||
this.ui.failedDownloadOptions.slideUp();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AsModelBoundView.call(view);
|
||||
AsValidatedView.call(view);
|
||||
|
||||
return view;
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
<fieldset class="advanced-setting">
|
||||
<legend>Failed Download Handling</legend>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">Enable</label>
|
||||
|
||||
<div class="controls">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="enableFailedDownloadHandling" class="x-failed-download-handling"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Process failed downloads and blacklist the release"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x-failed-download-options">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Redownload</label>
|
||||
|
||||
<div class="controls">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="autoRedownloadFailed"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Automatically search for and attempt to download another release when a download fails?"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">Remove</label>
|
||||
|
||||
<div class="controls">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="removeFailedDownloads"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Automatically remove failed downloads from history and encrypted downloads from queue?"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
|
@ -0,0 +1,26 @@
|
|||
'use strict';
|
||||
define(
|
||||
[
|
||||
'marionette',
|
||||
'Mixins/AsModelBoundView',
|
||||
'Mixins/AsValidatedView',
|
||||
'Mixins/AutoComplete'
|
||||
], function (Marionette, AsModelBoundView, AsValidatedView) {
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
template: 'Settings/DownloadClient/Options/DownloadClientOptionsViewTemplate',
|
||||
|
||||
ui: {
|
||||
droneFactory : '.x-path'
|
||||
},
|
||||
|
||||
onShow: function () {
|
||||
this.ui.droneFactory.autoComplete('/directories');
|
||||
}
|
||||
});
|
||||
|
||||
AsModelBoundView.call(view);
|
||||
AsValidatedView.call(view);
|
||||
|
||||
return view;
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
<fieldset">
|
||||
<legend>Options</legend>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Drone Factory</label>
|
||||
|
||||
<div class="controls">
|
||||
<input type="text" name="downloadedEpisodesFolder" class="x-path"/>
|
||||
<span class="help-inline">
|
||||
<i class="icon-nd-form-info" title="The folder where your download client downloads TV shows to (Completed Download Directory)"/>
|
||||
<i class="icon-nd-form-warning" title="Do not use the folder that contains some or all of your sorted and named TV shows - doing so could cause data loss"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
|
@ -5,9 +5,8 @@ define(
|
|||
], function (SettingsModelBase) {
|
||||
return SettingsModelBase.extend({
|
||||
|
||||
url : window.NzbDrone.ApiRoot + '/settings/host',
|
||||
url : window.NzbDrone.ApiRoot + '/config/host',
|
||||
successMessage: 'General settings saved',
|
||||
errorMessage : 'Failed to save general settings'
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
define(
|
||||
[
|
||||
'marionette',
|
||||
'Mixins/AsModelBoundView'
|
||||
], function (Marionette, AsModelBoundView) {
|
||||
'Mixins/AsModelBoundView',
|
||||
'Mixins/AsValidatedView'
|
||||
], function (Marionette, AsModelBoundView, AsValidatedView) {
|
||||
var view = Marionette.ItemView.extend({
|
||||
template: 'Settings/General/GeneralTemplate',
|
||||
template: 'Settings/General/GeneralViewTemplate',
|
||||
|
||||
events: {
|
||||
'change .x-auth': '_setAuthOptionsVisibility',
|
||||
|
@ -56,6 +57,9 @@ define(
|
|||
}
|
||||
});
|
||||
|
||||
return AsModelBoundView.call(view);
|
||||
AsModelBoundView.call(view);
|
||||
AsValidatedView.call(view);
|
||||
|
||||
return view;
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
define(
|
||||
[
|
||||
'Settings/SettingsModelBase'
|
||||
], function (SettingsModelBase) {
|
||||
return SettingsModelBase.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/config/indexer',
|
||||
successMessage: 'Indexer settings saved',
|
||||
errorMessage : 'Failed to save indexer settings'
|
||||
});
|
||||
});
|
|
@ -2,12 +2,16 @@
|
|||
define(
|
||||
[
|
||||
'marionette',
|
||||
'Mixins/AsModelBoundView'
|
||||
], function (Marionette, AsModelBoundView) {
|
||||
'Mixins/AsModelBoundView',
|
||||
'Mixins/AsValidatedView'
|
||||
], function (Marionette, AsModelBoundView, AsValidatedView) {
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
template: 'Settings/Indexers/Options/IndexerOptionsViewTemplate'
|
||||
});
|
||||
|
||||
return AsModelBoundView.call(view);
|
||||
AsModelBoundView.call(view);
|
||||
AsValidatedView.call(view);
|
||||
|
||||
return view;
|
||||
});
|
||||
|
|
|
@ -3,37 +3,24 @@ define(
|
|||
[
|
||||
'marionette',
|
||||
'Mixins/AsModelBoundView',
|
||||
'Mixins/AsValidatedView',
|
||||
'Mixins/AutoComplete'
|
||||
], function (Marionette, AsModelBoundView) {
|
||||
], function (Marionette, AsModelBoundView, AsValidatedView) {
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
template: 'Settings/MediaManagement/FileManagement/FileManagementViewTemplate',
|
||||
|
||||
ui: {
|
||||
recyclingBin : '.x-path',
|
||||
failedDownloadHandlingCheckbox: '.x-failed-download-handling',
|
||||
failedDownloadOptions : '.x-failed-download-options'
|
||||
},
|
||||
|
||||
events: {
|
||||
'change .x-failed-download-handling': '_setFailedDownloadOptionsVisibility'
|
||||
recyclingBin : '.x-path'
|
||||
},
|
||||
|
||||
onShow: function () {
|
||||
this.ui.recyclingBin.autoComplete('/directories');
|
||||
},
|
||||
|
||||
_setFailedDownloadOptionsVisibility: function () {
|
||||
var checked = this.ui.failedDownloadHandlingCheckbox.prop('checked');
|
||||
if (checked) {
|
||||
this.ui.failedDownloadOptions.slideDown();
|
||||
}
|
||||
|
||||
else {
|
||||
this.ui.failedDownloadOptions.slideUp();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return AsModelBoundView.call(view);
|
||||
AsModelBoundView.call(view);
|
||||
AsValidatedView.call(view);
|
||||
|
||||
return view;
|
||||
});
|
||||
|
|
|
@ -52,69 +52,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="advanced-setting">
|
||||
<legend>Failed Download Handling</legend>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">Enable</label>
|
||||
|
||||
<div class="controls">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="enableFailedDownloadHandling" class="x-failed-download-handling"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Process failed downloads and blacklist the release"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="x-failed-download-options">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Redownload</label>
|
||||
|
||||
<div class="controls">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="autoRedownloadFailed"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Automatically search for and attempt to download another release when a download fails?"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label">Remove</label>
|
||||
|
||||
<div class="controls">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="removeFailedDownloads"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-nd-form-info" title="Automatically remove failed downloads from history and encrypted downloads from queue?"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
|
@ -4,7 +4,7 @@ define(
|
|||
[
|
||||
'marionette',
|
||||
'Settings/MediaManagement/Naming/NamingView',
|
||||
'Settings/MediaManagement/Sorting/View',
|
||||
'Settings/MediaManagement/Sorting/SortingView',
|
||||
'Settings/MediaManagement/FileManagement/FileManagementView',
|
||||
'Settings/MediaManagement/Permissions/PermissionsView'
|
||||
], function (Marionette, NamingView, SortingView, FileManagementView, PermissionsView) {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
define(
|
||||
[
|
||||
'Settings/SettingsModelBase'
|
||||
], function (SettingsModelBase) {
|
||||
return SettingsModelBase.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/config/mediamanagement',
|
||||
successMessage: 'Media management settings saved',
|
||||
errorMessage : 'Failed to save media managemnent settings'
|
||||
});
|
||||
});
|
|
@ -3,8 +3,9 @@ define(
|
|||
[
|
||||
'marionette',
|
||||
'Mixins/AsModelBoundView',
|
||||
'Mixins/AsValidatedView',
|
||||
'Mixins/AutoComplete'
|
||||
], function (Marionette, AsModelBoundView) {
|
||||
], function (Marionette, AsModelBoundView, AsValidatedView) {
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
template: 'Settings/MediaManagement/Permissions/PermissionsViewTemplate',
|
||||
|
@ -35,5 +36,8 @@ define(
|
|||
}
|
||||
});
|
||||
|
||||
return AsModelBoundView.call(view);
|
||||
AsModelBoundView.call(view);
|
||||
AsValidatedView.call(view);
|
||||
|
||||
return view;
|
||||
});
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
'use strict';
|
||||
define(
|
||||
[
|
||||
'marionette',
|
||||
'Mixins/AsModelBoundView',
|
||||
'Mixins/AsValidatedView'
|
||||
], function (Marionette, AsModelBoundView, AsValidatedView) {
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
template: 'Settings/MediaManagement/Sorting/SortingViewTemplate'
|
||||
});
|
||||
|
||||
AsModelBoundView.call(view);
|
||||
AsValidatedView.call(view);
|
||||
|
||||
return view;
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
'use strict';
|
||||
define(
|
||||
[
|
||||
'marionette',
|
||||
'Mixins/AsModelBoundView'
|
||||
], function (Marionette, AsModelBoundView) {
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
template: 'Settings/MediaManagement/Sorting/ViewTemplate'
|
||||
});
|
||||
|
||||
return AsModelBoundView.call(view);
|
||||
});
|
|
@ -2,17 +2,20 @@
|
|||
define(
|
||||
[
|
||||
'jquery',
|
||||
'underscore',
|
||||
'vent',
|
||||
'marionette',
|
||||
'backbone',
|
||||
'Settings/SettingsModel',
|
||||
'Settings/General/GeneralSettingsModel',
|
||||
'Settings/MediaManagement/Naming/NamingModel',
|
||||
'Settings/MediaManagement/MediaManagementLayout',
|
||||
'Settings/MediaManagement/MediaManagementSettingsModel',
|
||||
'Settings/Quality/QualityLayout',
|
||||
'Settings/Indexers/IndexerLayout',
|
||||
'Settings/Indexers/Collection',
|
||||
'Settings/Indexers/IndexerSettingsModel',
|
||||
'Settings/DownloadClient/DownloadClientLayout',
|
||||
'Settings/DownloadClient/DownloadClientSettingsModel',
|
||||
'Settings/Notifications/CollectionView',
|
||||
'Settings/Notifications/Collection',
|
||||
'Settings/Metadata/MetadataLayout',
|
||||
|
@ -20,17 +23,20 @@ define(
|
|||
'Shared/LoadingView',
|
||||
'Config'
|
||||
], function ($,
|
||||
_,
|
||||
vent,
|
||||
Marionette,
|
||||
Backbone,
|
||||
SettingsModel,
|
||||
GeneralSettingsModel,
|
||||
NamingModel,
|
||||
MediaManagementLayout,
|
||||
MediaManagementSettingsModel,
|
||||
QualityLayout,
|
||||
IndexerLayout,
|
||||
IndexerCollection,
|
||||
IndexerSettingsModel,
|
||||
DownloadClientLayout,
|
||||
DownloadClientSettingsModel,
|
||||
NotificationCollectionView,
|
||||
NotificationCollection,
|
||||
MetadataLayout,
|
||||
|
@ -84,26 +90,31 @@ define(
|
|||
this.loading.show(new LoadingView());
|
||||
var self = this;
|
||||
|
||||
this.settings = new SettingsModel();
|
||||
this.generalSettings = new GeneralSettingsModel();
|
||||
this.mediaManagementSettings = new MediaManagementSettingsModel();
|
||||
this.namingSettings = new NamingModel();
|
||||
this.indexerSettings = new IndexerCollection();
|
||||
this.notificationSettings = new NotificationCollection();
|
||||
this.indexerSettings = new IndexerSettingsModel();
|
||||
this.indexerCollection = new IndexerCollection();
|
||||
this.downloadClientSettings = new DownloadClientSettingsModel();
|
||||
this.notificationCollection = new NotificationCollection();
|
||||
this.generalSettings = new GeneralSettingsModel();
|
||||
|
||||
Backbone.$.when(this.settings.fetch(),
|
||||
this.generalSettings.fetch(),
|
||||
Backbone.$.when(
|
||||
this.mediaManagementSettings.fetch(),
|
||||
this.namingSettings.fetch(),
|
||||
this.indexerSettings.fetch(),
|
||||
this.notificationSettings.fetch()
|
||||
this.indexerCollection.fetch(),
|
||||
this.downloadClientSettings.fetch(),
|
||||
this.notificationCollection.fetch(),
|
||||
this.generalSettings.fetch()
|
||||
).done(function () {
|
||||
if(!self.isClosed)
|
||||
{
|
||||
self.loading.$el.hide();
|
||||
self.mediaManagement.show(new MediaManagementLayout({ settings: self.settings, namingSettings: self.namingSettings }));
|
||||
self.quality.show(new QualityLayout({ settings: self.settings }));
|
||||
self.indexers.show(new IndexerLayout({ settings: self.settings, indexersCollection: self.indexerSettings }));
|
||||
self.downloadClient.show(new DownloadClientLayout({ model: self.settings }));
|
||||
self.notifications.show(new NotificationCollectionView({ collection: self.notificationSettings }));
|
||||
self.mediaManagement.show(new MediaManagementLayout({ settings: self.mediaManagementSettings, namingSettings: self.namingSettings }));
|
||||
self.quality.show(new QualityLayout());
|
||||
self.indexers.show(new IndexerLayout({ settings: self.indexerSettings, indexersCollection: self.indexerCollection }));
|
||||
self.downloadClient.show(new DownloadClientLayout({ model: self.downloadClientSettings }));
|
||||
self.notifications.show(new NotificationCollectionView({ collection: self.notificationCollection }));
|
||||
self.metadata.show(new MetadataLayout());
|
||||
self.general.show(new GeneralView({ model: self.generalSettings }));
|
||||
}
|
||||
|
@ -204,7 +215,7 @@ define(
|
|||
},
|
||||
|
||||
_navigate:function(route){
|
||||
Backbone.history.navigate(route, { trigger: true, replace: true });
|
||||
Backbone.history.navigate(route, { trigger: false, replace: true });
|
||||
},
|
||||
|
||||
_save: function () {
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
'use strict';
|
||||
define(
|
||||
[
|
||||
'Settings/SettingsModelBase'
|
||||
], function (SettingsModelBase) {
|
||||
return SettingsModelBase.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/settings',
|
||||
successMessage: 'Settings saved',
|
||||
errorMessage : 'Failed to save settings'
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue