mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-05 02:22:31 +01:00
Merge branch 'develop'
This commit is contained in:
commit
3e545a9d62
@ -73,6 +73,7 @@ module.exports = function (grunt) {
|
|||||||
'UI/Cells/cells.less',
|
'UI/Cells/cells.less',
|
||||||
'UI/Logs/logs.less',
|
'UI/Logs/logs.less',
|
||||||
'UI/Settings/settings.less',
|
'UI/Settings/settings.less',
|
||||||
|
'UI/Update/update.less'
|
||||||
],
|
],
|
||||||
dest : outputRoot,
|
dest : outputRoot,
|
||||||
ext: '.css'
|
ext: '.css'
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using Nancy.Authentication.Basic;
|
using System;
|
||||||
|
using Nancy;
|
||||||
|
using Nancy.Authentication.Basic;
|
||||||
using Nancy.Security;
|
using Nancy.Security;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
@ -7,6 +9,7 @@ namespace NzbDrone.Api.Authentication
|
|||||||
public interface IAuthenticationService : IUserValidator
|
public interface IAuthenticationService : IUserValidator
|
||||||
{
|
{
|
||||||
bool Enabled { get; }
|
bool Enabled { get; }
|
||||||
|
bool IsAuthenticated(NancyContext context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AuthenticationService : IAuthenticationService
|
public class AuthenticationService : IAuthenticationService
|
||||||
@ -44,5 +47,12 @@ public bool Enabled
|
|||||||
return _configFileProvider.AuthenticationEnabled;
|
return _configFileProvider.AuthenticationEnabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsAuthenticated(NancyContext context)
|
||||||
|
{
|
||||||
|
if (context.CurrentUser == null && _configFileProvider.AuthenticationEnabled) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
using Nancy;
|
using Nancy;
|
||||||
using Nancy.Authentication.Basic;
|
using Nancy.Authentication.Basic;
|
||||||
using Nancy.Bootstrapper;
|
using Nancy.Bootstrapper;
|
||||||
|
using NzbDrone.Api.Extensions;
|
||||||
|
using NzbDrone.Api.Extensions.Pipelines;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Authentication
|
namespace NzbDrone.Api.Authentication
|
||||||
{
|
{
|
||||||
public interface IEnableBasicAuthInNancy
|
public class EnableBasicAuthInNancy : IRegisterNancyPipeline
|
||||||
{
|
|
||||||
void Register(IPipelines pipelines);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class EnableBasicAuthInNancy : IEnableBasicAuthInNancy
|
|
||||||
{
|
{
|
||||||
private readonly IAuthenticationService _authenticationService;
|
private readonly IAuthenticationService _authenticationService;
|
||||||
|
|
||||||
@ -27,7 +24,8 @@ public void Register(IPipelines pipelines)
|
|||||||
private Response RequiresAuthentication(NancyContext context)
|
private Response RequiresAuthentication(NancyContext context)
|
||||||
{
|
{
|
||||||
Response response = null;
|
Response response = null;
|
||||||
if (context.CurrentUser == null && _authenticationService.Enabled)
|
|
||||||
|
if (!context.Request.IsApiRequest() && !_authenticationService.IsAuthenticated(context))
|
||||||
{
|
{
|
||||||
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
||||||
}
|
}
|
||||||
|
55
NzbDrone.Api/Authentication/EnableStatelessAuthInNancy.cs
Normal file
55
NzbDrone.Api/Authentication/EnableStatelessAuthInNancy.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Nancy;
|
||||||
|
using Nancy.Bootstrapper;
|
||||||
|
using NzbDrone.Api.Extensions;
|
||||||
|
using NzbDrone.Api.Extensions.Pipelines;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Authentication
|
||||||
|
{
|
||||||
|
public class EnableStatelessAuthInNancy : IRegisterNancyPipeline
|
||||||
|
{
|
||||||
|
private readonly IAuthenticationService _authenticationService;
|
||||||
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
|
||||||
|
public EnableStatelessAuthInNancy(IAuthenticationService authenticationService, IConfigFileProvider configFileProvider)
|
||||||
|
{
|
||||||
|
_authenticationService = authenticationService;
|
||||||
|
_configFileProvider = configFileProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register(IPipelines pipelines)
|
||||||
|
{
|
||||||
|
pipelines.BeforeRequest.AddItemToEndOfPipeline(ValidateApiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response ValidateApiKey(NancyContext context)
|
||||||
|
{
|
||||||
|
Response response = null;
|
||||||
|
|
||||||
|
if (!RuntimeInfo.IsProduction && context.Request.IsLocalRequest())
|
||||||
|
{
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiKey = context.Request.Headers.Authorization;
|
||||||
|
|
||||||
|
if (context.Request.IsApiRequest() && !ValidApiKey(apiKey) && !_authenticationService.IsAuthenticated(context))
|
||||||
|
{
|
||||||
|
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ValidApiKey(string apiKey)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrWhiteSpace(apiKey)) return false;
|
||||||
|
if (!apiKey.Equals(_configFileProvider.ApiKey)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
NzbDrone.Api/Extensions/RequestExtensions.cs
Normal file
28
NzbDrone.Api/Extensions/RequestExtensions.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Nancy;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Extensions
|
||||||
|
{
|
||||||
|
public static class RequestExtensions
|
||||||
|
{
|
||||||
|
public static bool IsApiRequest(this Request request)
|
||||||
|
{
|
||||||
|
return request.Path.StartsWith("/api/", StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsSignalRRequest(this Request request)
|
||||||
|
{
|
||||||
|
return request.Path.StartsWith("/signalr/", StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsLocalRequest(this Request request)
|
||||||
|
{
|
||||||
|
return (request.UserHostAddress.Equals("localhost") ||
|
||||||
|
request.UserHostAddress.Equals("127.0.0.1") ||
|
||||||
|
request.UserHostAddress.Equals("::1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,28 @@
|
|||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Nancy;
|
using Nancy;
|
||||||
|
using Nancy.Responses;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Frontend.Mappers
|
namespace NzbDrone.Api.Frontend.Mappers
|
||||||
{
|
{
|
||||||
public class IndexHtmlMapper : StaticResourceMapperBase
|
public class IndexHtmlMapper : StaticResourceMapperBase
|
||||||
{
|
{
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
private readonly string _indexPath;
|
private readonly string _indexPath;
|
||||||
|
|
||||||
public IndexHtmlMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger)
|
public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
|
||||||
|
IDiskProvider diskProvider,
|
||||||
|
IConfigFileProvider configFileProvider,
|
||||||
|
Logger logger)
|
||||||
: base(diskProvider, logger)
|
: base(diskProvider, logger)
|
||||||
{
|
{
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
|
_configFileProvider = configFileProvider;
|
||||||
_indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "index.html");
|
_indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "index.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,9 +56,9 @@ private string GetIndexText()
|
|||||||
|
|
||||||
text = text.Replace(".css", ".css?v=" + BuildInfo.Version);
|
text = text.Replace(".css", ".css?v=" + BuildInfo.Version);
|
||||||
text = text.Replace(".js", ".js?v=" + BuildInfo.Version);
|
text = text.Replace(".js", ".js?v=" + BuildInfo.Version);
|
||||||
|
text = text.Replace("API_KEY", _configFileProvider.ApiKey);
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,13 +24,10 @@ protected StaticResourceMapperBase(IDiskProvider diskProvider, Logger logger)
|
|||||||
{
|
{
|
||||||
_caseSensitive = true;
|
_caseSensitive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract string Map(string resourceUrl);
|
protected abstract string Map(string resourceUrl);
|
||||||
|
|
||||||
|
|
||||||
public abstract bool CanHandle(string resourceUrl);
|
public abstract bool CanHandle(string resourceUrl);
|
||||||
|
|
||||||
public virtual Response GetResponse(string resourceUrl)
|
public virtual Response GetResponse(string resourceUrl)
|
||||||
|
@ -30,7 +30,6 @@ protected override void ApplicationStartup(TinyIoCContainer container, IPipeline
|
|||||||
RegisterPipelines(pipelines);
|
RegisterPipelines(pipelines);
|
||||||
|
|
||||||
container.Resolve<DatabaseTarget>().Register();
|
container.Resolve<DatabaseTarget>().Register();
|
||||||
container.Resolve<IEnableBasicAuthInNancy>().Register(pipelines);
|
|
||||||
container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartedEvent());
|
container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartedEvent());
|
||||||
|
|
||||||
ApplicationPipelines.OnError.AddItemToEndOfPipeline(container.Resolve<NzbDroneErrorPipeline>().HandleException);
|
ApplicationPipelines.OnError.AddItemToEndOfPipeline(container.Resolve<NzbDroneErrorPipeline>().HandleException);
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Authentication\AuthenticationService.cs" />
|
<Compile Include="Authentication\AuthenticationService.cs" />
|
||||||
|
<Compile Include="Authentication\EnableStatelessAuthInNancy.cs" />
|
||||||
<Compile Include="Authentication\EnableBasicAuthInNancy.cs" />
|
<Compile Include="Authentication\EnableBasicAuthInNancy.cs" />
|
||||||
<Compile Include="Authentication\NzbDroneUser.cs" />
|
<Compile Include="Authentication\NzbDroneUser.cs" />
|
||||||
<Compile Include="Calendar\CalendarModule.cs" />
|
<Compile Include="Calendar\CalendarModule.cs" />
|
||||||
@ -97,6 +98,7 @@
|
|||||||
<Compile Include="Extensions\Pipelines\IfModifiedPipeline.cs" />
|
<Compile Include="Extensions\Pipelines\IfModifiedPipeline.cs" />
|
||||||
<Compile Include="Extensions\Pipelines\IRegisterNancyPipeline.cs" />
|
<Compile Include="Extensions\Pipelines\IRegisterNancyPipeline.cs" />
|
||||||
<Compile Include="Extensions\NancyJsonSerializer.cs" />
|
<Compile Include="Extensions\NancyJsonSerializer.cs" />
|
||||||
|
<Compile Include="Extensions\RequestExtensions.cs" />
|
||||||
<Compile Include="Frontend\IsCacheableSpecification.cs" />
|
<Compile Include="Frontend\IsCacheableSpecification.cs" />
|
||||||
<Compile Include="Frontend\Mappers\IndexHtmlMapper.cs" />
|
<Compile Include="Frontend\Mappers\IndexHtmlMapper.cs" />
|
||||||
<Compile Include="Frontend\Mappers\LogFileMapper.cs" />
|
<Compile Include="Frontend\Mappers\LogFileMapper.cs" />
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NzbDrone.Api.REST;
|
using NzbDrone.Api.REST;
|
||||||
using NzbDrone.Core.MediaCover;
|
using NzbDrone.Core.MediaCover;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
@ -19,9 +20,9 @@ public Int32 SeasonCount
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (Seasons != null) return Seasons.Count;
|
if (Seasons == null) return 0;
|
||||||
|
|
||||||
return 0;
|
return Seasons.Where(s => s.SeasonNumber > 0).Count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,25 +10,33 @@ namespace NzbDrone.Api.Update
|
|||||||
public class UpdateModule : NzbDroneRestModule<UpdateResource>
|
public class UpdateModule : NzbDroneRestModule<UpdateResource>
|
||||||
{
|
{
|
||||||
private readonly ICheckUpdateService _checkUpdateService;
|
private readonly ICheckUpdateService _checkUpdateService;
|
||||||
|
private readonly IRecentUpdateProvider _recentUpdateProvider;
|
||||||
|
|
||||||
public UpdateModule(ICheckUpdateService checkUpdateService)
|
public UpdateModule(ICheckUpdateService checkUpdateService,
|
||||||
|
IRecentUpdateProvider recentUpdateProvider)
|
||||||
{
|
{
|
||||||
_checkUpdateService = checkUpdateService;
|
_checkUpdateService = checkUpdateService;
|
||||||
GetResourceAll = GetAvailableUpdate;
|
_recentUpdateProvider = recentUpdateProvider;
|
||||||
|
GetResourceAll = GetRecentUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<UpdateResource> GetAvailableUpdate()
|
private UpdateResource GetAvailableUpdate()
|
||||||
{
|
{
|
||||||
var update = _checkUpdateService.AvailableUpdate();
|
var update = _checkUpdateService.AvailableUpdate();
|
||||||
var response = new List<UpdateResource>();
|
var response = new UpdateResource();
|
||||||
|
|
||||||
if (update != null)
|
if (update != null)
|
||||||
{
|
{
|
||||||
response.Add(update.InjectTo<UpdateResource>());
|
return update.InjectTo<UpdateResource>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<UpdateResource> GetRecentUpdates()
|
||||||
|
{
|
||||||
|
return ToListResource(_recentUpdateProvider.GetRecentUpdatePackages);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpdateResource : RestResource
|
public class UpdateResource : RestResource
|
||||||
@ -40,5 +48,7 @@ public class UpdateResource : RestResource
|
|||||||
public DateTime ReleaseDate { get; set; }
|
public DateTime ReleaseDate { get; set; }
|
||||||
public String FileName { get; set; }
|
public String FileName { get; set; }
|
||||||
public String Url { get; set; }
|
public String Url { get; set; }
|
||||||
|
|
||||||
|
public UpdateChanges Changes { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -39,6 +39,7 @@ public interface IDiskProvider
|
|||||||
string GetPathRoot(string path);
|
string GetPathRoot(string path);
|
||||||
void SetPermissions(string filename, WellKnownSidType accountSid, FileSystemRights rights, AccessControlType controlType);
|
void SetPermissions(string filename, WellKnownSidType accountSid, FileSystemRights rights, AccessControlType controlType);
|
||||||
bool IsParent(string parentPath, string childPath);
|
bool IsParent(string parentPath, string childPath);
|
||||||
|
void SetFolderWriteTime(string path, DateTime time);
|
||||||
FileAttributes GetFileAttributes(string path);
|
FileAttributes GetFileAttributes(string path);
|
||||||
void EmptyFolder(string path);
|
void EmptyFolder(string path);
|
||||||
}
|
}
|
||||||
@ -441,6 +442,10 @@ public bool IsParent(string parentPath, string childPath)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetFolderWriteTime(string path, DateTime time)
|
||||||
|
{
|
||||||
|
Directory.SetLastWriteTimeUtc(path, time);
|
||||||
|
}
|
||||||
|
|
||||||
private static void RemoveReadOnly(string path)
|
private static void RemoveReadOnly(string path)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<error code="100" description="Incorrect user credentials"/>
|
@ -252,6 +252,7 @@
|
|||||||
<Content Include="App_Data\Config.xml">
|
<Content Include="App_Data\Config.xml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="Files\Indexers\Newznab\unauthorized.xml" />
|
||||||
<Content Include="Files\Media\H264_sample.mp4">
|
<Content Include="Files\Media\H264_sample.mp4">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
@ -351,7 +352,6 @@
|
|||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Files\Indexers\" />
|
|
||||||
<Folder Include="ProviderTests\UpdateProviderTests\" />
|
<Folder Include="ProviderTests\UpdateProviderTests\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
Binary file not shown.
@ -28,13 +28,14 @@ public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>
|
|||||||
string Password { get; }
|
string Password { get; }
|
||||||
string LogLevel { get; }
|
string LogLevel { get; }
|
||||||
string Branch { get; }
|
string Branch { get; }
|
||||||
|
string ApiKey { get; }
|
||||||
bool Torrent { get; }
|
bool Torrent { get; }
|
||||||
string SslCertHash { get; }
|
string SslCertHash { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConfigFileProvider : IConfigFileProvider
|
public class ConfigFileProvider : IConfigFileProvider
|
||||||
{
|
{
|
||||||
private const string CONFIG_ELEMENT_NAME = "Config";
|
public const string CONFIG_ELEMENT_NAME = "Config";
|
||||||
|
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly ICached<string> _cache;
|
private readonly ICached<string> _cache;
|
||||||
@ -108,6 +109,14 @@ public bool LaunchBrowser
|
|||||||
get { return GetValueBoolean("LaunchBrowser", true); }
|
get { return GetValueBoolean("LaunchBrowser", true); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ApiKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return GetValue("ApiKey", Guid.NewGuid().ToString().Replace("-", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool Torrent
|
public bool Torrent
|
||||||
{
|
{
|
||||||
get { return GetValueBoolean("Torrent", false, persist: false); }
|
get { return GetValueBoolean("Torrent", false, persist: false); }
|
||||||
@ -223,6 +232,8 @@ private void EnsureDefaultConfigFile()
|
|||||||
var xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
|
var xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
|
||||||
xDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
|
xDoc.Add(new XElement(CONFIG_ELEMENT_NAME));
|
||||||
xDoc.Save(_configFile);
|
xDoc.Save(_configFile);
|
||||||
|
|
||||||
|
SaveConfigDictionary(GetConfigDictionary());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,11 +4,11 @@ namespace NzbDrone.Core.Exceptions
|
|||||||
{
|
{
|
||||||
public class BadRequestException : DownstreamException
|
public class BadRequestException : DownstreamException
|
||||||
{
|
{
|
||||||
public BadRequestException(HttpStatusCode statusCode, string message) : base(statusCode, message)
|
public BadRequestException(string message) : base(HttpStatusCode.BadRequest, message)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public BadRequestException(HttpStatusCode statusCode, string message, params object[] args) : base(statusCode, message, args)
|
public BadRequestException(string message, params object[] args) : base(HttpStatusCode.BadRequest, message, args)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ public static void VerifyStatusCode(this HttpStatusCode statusCode, string messa
|
|||||||
switch (statusCode)
|
switch (statusCode)
|
||||||
{
|
{
|
||||||
case HttpStatusCode.BadRequest:
|
case HttpStatusCode.BadRequest:
|
||||||
throw new BadRequestException(statusCode, message);
|
throw new BadRequestException(message);
|
||||||
|
|
||||||
case HttpStatusCode.Unauthorized:
|
case HttpStatusCode.Unauthorized:
|
||||||
throw new UnauthorizedAccessException(message);
|
throw new UnauthorizedAccessException(message);
|
||||||
|
19
NzbDrone.Core/Indexers/Exceptions/ApiKeyException.cs
Normal file
19
NzbDrone.Core/Indexers/Exceptions/ApiKeyException.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Indexers.Exceptions
|
||||||
|
{
|
||||||
|
public class ApiKeyException : NzbDroneException
|
||||||
|
{
|
||||||
|
public ApiKeyException(string message, params object[] args) : base(message, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiKeyException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Core.Indexers.Exceptions;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -30,7 +31,6 @@ public FetchFeedService(IHttpProvider httpProvider, Logger logger)
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public virtual IList<ReleaseInfo> FetchRss(IIndexer indexer)
|
public virtual IList<ReleaseInfo> FetchRss(IIndexer indexer)
|
||||||
{
|
{
|
||||||
_logger.Debug("Fetching feeds from " + indexer.Name);
|
_logger.Debug("Fetching feeds from " + indexer.Name);
|
||||||
@ -53,7 +53,6 @@ public IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCri
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria, int offset)
|
private IList<ReleaseInfo> Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria, int offset)
|
||||||
{
|
{
|
||||||
_logger.Debug("Searching for {0} offset: {1}", searchCriteria, offset);
|
_logger.Debug("Searching for {0} offset: {1}", searchCriteria, offset);
|
||||||
@ -117,15 +116,21 @@ private List<ReleaseInfo> Fetch(IIndexer indexer, IEnumerable<string> urls)
|
|||||||
}
|
}
|
||||||
catch (WebException webException)
|
catch (WebException webException)
|
||||||
{
|
{
|
||||||
if (webException.Message.Contains("502") || webException.Message.Contains("503") || webException.Message.Contains("timed out"))
|
if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
|
||||||
|
webException.Message.Contains("timed out"))
|
||||||
{
|
{
|
||||||
_logger.Warn("{0} server is currently unavailable. {1} {2}", indexer.Name, url, webException.Message);
|
_logger.Warn("{0} server is currently unavailable. {1} {2}", indexer.Name, url,
|
||||||
|
webException.Message);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Warn("{0} {1} {2}", indexer.Name, url, webException.Message);
|
_logger.Warn("{0} {1} {2}", indexer.Name, url, webException.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (ApiKeyException)
|
||||||
|
{
|
||||||
|
_logger.Warn("Invalid API Key for {0} {1}", indexer.Name, url);
|
||||||
|
}
|
||||||
catch (Exception feedEx)
|
catch (Exception feedEx)
|
||||||
{
|
{
|
||||||
feedEx.Data.Add("FeedUrl", url);
|
feedEx.Data.Add("FeedUrl", url);
|
||||||
|
@ -37,14 +37,20 @@ public class IndexerService : IIndexerService, IHandle<ApplicationStartedEvent>
|
|||||||
{
|
{
|
||||||
private readonly IIndexerRepository _indexerRepository;
|
private readonly IIndexerRepository _indexerRepository;
|
||||||
private readonly IConfigFileProvider _configFileProvider;
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
private readonly INewznabTestService _newznabTestService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
private readonly List<IIndexer> _indexers;
|
private readonly List<IIndexer> _indexers;
|
||||||
|
|
||||||
public IndexerService(IIndexerRepository indexerRepository, IEnumerable<IIndexer> indexers, IConfigFileProvider configFileProvider, Logger logger)
|
public IndexerService(IIndexerRepository indexerRepository,
|
||||||
|
IEnumerable<IIndexer> indexers,
|
||||||
|
IConfigFileProvider configFileProvider,
|
||||||
|
INewznabTestService newznabTestService,
|
||||||
|
Logger logger)
|
||||||
{
|
{
|
||||||
_indexerRepository = indexerRepository;
|
_indexerRepository = indexerRepository;
|
||||||
_configFileProvider = configFileProvider;
|
_configFileProvider = configFileProvider;
|
||||||
|
_newznabTestService = newznabTestService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
|
|
||||||
@ -104,6 +110,9 @@ public Indexer Create(Indexer indexer)
|
|||||||
Settings = indexer.Settings.ToJson()
|
Settings = indexer.Settings.ToJson()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var instance = ToIndexer(definition).Instance;
|
||||||
|
_newznabTestService.Test(instance);
|
||||||
|
|
||||||
definition = _indexerRepository.Insert(definition);
|
definition = _indexerRepository.Insert(definition);
|
||||||
indexer.Id = definition.Id;
|
indexer.Id = definition.Id;
|
||||||
|
|
||||||
|
@ -114,7 +114,6 @@ public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int
|
|||||||
return RecentFeed.Select(url => String.Format("{0}&limit=100&q={1}&season={2}&offset={3}", url, NewsnabifyTitle(seriesTitle), seasonNumber, offset));
|
return RecentFeed.Select(url => String.Format("{0}&limit=100&q={1}&season={2}&offset={3}", url, NewsnabifyTitle(seriesTitle), seasonNumber, offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override string Name
|
public override string Name
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -131,7 +130,6 @@ public override IndexerKind Kind
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static string NewsnabifyTitle(string title)
|
private static string NewsnabifyTitle(string title)
|
||||||
{
|
{
|
||||||
return title.Replace("+", "%20");
|
return title.Replace("+", "%20");
|
||||||
|
19
NzbDrone.Core/Indexers/Newznab/NewznabException.cs
Normal file
19
NzbDrone.Core/Indexers/Newznab/NewznabException.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Indexers.Newznab
|
||||||
|
{
|
||||||
|
public class NewznabException : NzbDroneException
|
||||||
|
{
|
||||||
|
public NewznabException(string message, params object[] args) : base(message, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public NewznabException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
using NzbDrone.Core.Indexers.Exceptions;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.Newznab
|
namespace NzbDrone.Core.Indexers.Newznab
|
||||||
@ -46,5 +48,10 @@ protected override ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentR
|
|||||||
|
|
||||||
return currentResult;
|
return currentResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void PreProcess(string source, string url)
|
||||||
|
{
|
||||||
|
NewznabPreProcessor.Process(source, url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
24
NzbDrone.Core/Indexers/Newznab/NewznabPreProcessor.cs
Normal file
24
NzbDrone.Core/Indexers/Newznab/NewznabPreProcessor.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using NzbDrone.Core.Indexers.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Indexers.Newznab
|
||||||
|
{
|
||||||
|
public static class NewznabPreProcessor
|
||||||
|
{
|
||||||
|
public static void Process(string source, string url)
|
||||||
|
{
|
||||||
|
var xdoc = XDocument.Parse(source);
|
||||||
|
var error = xdoc.Descendants("error").FirstOrDefault();
|
||||||
|
|
||||||
|
if (error == null) return;
|
||||||
|
|
||||||
|
var code = Convert.ToInt32(error.Attribute("code").Value);
|
||||||
|
|
||||||
|
if (code >= 100 && code <= 199) throw new ApiKeyException("Invalid API key: {0}");
|
||||||
|
|
||||||
|
throw new NewznabException("Newznab error detected: {0}", error.Attribute("description").Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
NzbDrone.Core/Indexers/NewznabTestService.cs
Normal file
60
NzbDrone.Core/Indexers/NewznabTestService.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using FluentValidation;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Core.Indexers.Exceptions;
|
||||||
|
using NzbDrone.Core.Indexers.Newznab;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Indexers
|
||||||
|
{
|
||||||
|
public interface INewznabTestService
|
||||||
|
{
|
||||||
|
void Test(IIndexer indexer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NewznabTestService : INewznabTestService
|
||||||
|
{
|
||||||
|
private readonly IFetchFeedFromIndexers _feedFetcher;
|
||||||
|
private readonly IHttpProvider _httpProvider;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public NewznabTestService(IFetchFeedFromIndexers feedFetcher, IHttpProvider httpProvider, Logger logger)
|
||||||
|
{
|
||||||
|
_feedFetcher = feedFetcher;
|
||||||
|
_httpProvider = httpProvider;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Test(IIndexer indexer)
|
||||||
|
{
|
||||||
|
var releases = _feedFetcher.FetchRss(indexer);
|
||||||
|
|
||||||
|
if (releases.Any()) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var url = indexer.RecentFeed.First();
|
||||||
|
var xml = _httpProvider.DownloadString(url);
|
||||||
|
|
||||||
|
NewznabPreProcessor.Process(xml, url);
|
||||||
|
}
|
||||||
|
catch (ApiKeyException apiKeyException)
|
||||||
|
{
|
||||||
|
_logger.Warn("Indexer returned result for Newznab RSS URL, API Key appears to be invalid");
|
||||||
|
|
||||||
|
var apiKeyFailure = new ValidationFailure("ApiKey", "Invalid API Key");
|
||||||
|
throw new ValidationException(new List<ValidationFailure> { apiKeyFailure }.ToArray());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Warn("Indexer doesn't appear to be Newznab based");
|
||||||
|
|
||||||
|
var failure = new ValidationFailure("Url", "Invalid Newznab URL entered");
|
||||||
|
throw new ValidationException(new List<ValidationFailure> { failure }.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,8 @@ protected RssParserBase()
|
|||||||
|
|
||||||
public IEnumerable<ReleaseInfo> Process(string xml, string url)
|
public IEnumerable<ReleaseInfo> Process(string xml, string url)
|
||||||
{
|
{
|
||||||
|
PreProcess(xml, url);
|
||||||
|
|
||||||
using (var xmlTextReader = XmlReader.Create(new StringReader(xml), new XmlReaderSettings { ProhibitDtd = false, IgnoreComments = true }))
|
using (var xmlTextReader = XmlReader.Create(new StringReader(xml), new XmlReaderSettings { ProhibitDtd = false, IgnoreComments = true }))
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -103,6 +105,10 @@ protected virtual string GetNzbInfoUrl(XElement item)
|
|||||||
|
|
||||||
protected abstract long GetSize(XElement item);
|
protected abstract long GetSize(XElement item);
|
||||||
|
|
||||||
|
protected virtual void PreProcess(string source, string url)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentResult)
|
protected virtual ReleaseInfo PostProcessor(XElement item, ReleaseInfo currentResult)
|
||||||
{
|
{
|
||||||
return currentResult;
|
return currentResult;
|
||||||
|
@ -42,7 +42,7 @@ public string MoveEpisodeFile(EpisodeFile episodeFile, Series series)
|
|||||||
var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id);
|
var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id);
|
||||||
var newFileName = _buildFileNames.BuildFilename(episodes, series, episodeFile);
|
var newFileName = _buildFileNames.BuildFilename(episodes, series, episodeFile);
|
||||||
var filePath = _buildFileNames.BuildFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path));
|
var filePath = _buildFileNames.BuildFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path));
|
||||||
MoveFile(episodeFile, filePath);
|
MoveFile(episodeFile, series, filePath);
|
||||||
|
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
@ -51,12 +51,12 @@ public string MoveEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode
|
|||||||
{
|
{
|
||||||
var newFileName = _buildFileNames.BuildFilename(localEpisode.Episodes, localEpisode.Series, episodeFile);
|
var newFileName = _buildFileNames.BuildFilename(localEpisode.Episodes, localEpisode.Series, episodeFile);
|
||||||
var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path));
|
var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path));
|
||||||
MoveFile(episodeFile, filePath);
|
MoveFile(episodeFile, localEpisode.Series, filePath);
|
||||||
|
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MoveFile(EpisodeFile episodeFile, string destinationFilename)
|
private void MoveFile(EpisodeFile episodeFile, Series series, string destinationFilename)
|
||||||
{
|
{
|
||||||
if (!_diskProvider.FileExists(episodeFile.Path))
|
if (!_diskProvider.FileExists(episodeFile.Path))
|
||||||
{
|
{
|
||||||
@ -73,6 +73,17 @@ private void MoveFile(EpisodeFile episodeFile, string destinationFilename)
|
|||||||
_logger.Debug("Moving [{0}] > [{1}]", episodeFile.Path, destinationFilename);
|
_logger.Debug("Moving [{0}] > [{1}]", episodeFile.Path, destinationFilename);
|
||||||
_diskProvider.MoveFile(episodeFile.Path, destinationFilename);
|
_diskProvider.MoveFile(episodeFile.Path, destinationFilename);
|
||||||
|
|
||||||
|
_logger.Trace("Setting last write time on series folder: {0}", series.Path);
|
||||||
|
_diskProvider.SetFolderWriteTime(series.Path, episodeFile.DateAdded);
|
||||||
|
|
||||||
|
if (series.SeasonFolder)
|
||||||
|
{
|
||||||
|
var seasonFolder = Path.GetDirectoryName(destinationFilename);
|
||||||
|
|
||||||
|
_logger.Trace("Setting last write time on season folder: {0}", seasonFolder);
|
||||||
|
_diskProvider.SetFolderWriteTime(seasonFolder, episodeFile.DateAdded);
|
||||||
|
}
|
||||||
|
|
||||||
//Wrapped in Try/Catch to prevent this from causing issues with remote NAS boxes, the move worked, which is more important.
|
//Wrapped in Try/Catch to prevent this from causing issues with remote NAS boxes, the move worked, which is more important.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -71,7 +71,7 @@ private static Series MapSeries(Show show)
|
|||||||
series.ImdbId = show.imdb_id;
|
series.ImdbId = show.imdb_id;
|
||||||
series.Title = show.title;
|
series.Title = show.title;
|
||||||
series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.title);
|
series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.title);
|
||||||
series.Year = show.year;
|
series.Year = GetYear(show.year, show.first_aired);
|
||||||
series.FirstAired = FromIso(show.first_aired_iso);
|
series.FirstAired = FromIso(show.first_aired_iso);
|
||||||
series.Overview = show.overview;
|
series.Overview = show.overview;
|
||||||
series.Runtime = show.runtime;
|
series.Runtime = show.runtime;
|
||||||
@ -180,5 +180,14 @@ private static string GetSearchTerm(string phrase)
|
|||||||
|
|
||||||
return phrase;
|
return phrase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int GetYear(int year, int firstAired)
|
||||||
|
{
|
||||||
|
if (year > 1969) return year;
|
||||||
|
|
||||||
|
if (firstAired == 0) return DateTime.Today.Year;
|
||||||
|
|
||||||
|
return year;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,12 +234,16 @@
|
|||||||
<Compile Include="IndexerSearch\SeasonSearchService.cs" />
|
<Compile Include="IndexerSearch\SeasonSearchService.cs" />
|
||||||
<Compile Include="Indexers\BasicTorrentRssParser.cs" />
|
<Compile Include="Indexers\BasicTorrentRssParser.cs" />
|
||||||
<Compile Include="Indexers\DownloadProtocols.cs" />
|
<Compile Include="Indexers\DownloadProtocols.cs" />
|
||||||
|
<Compile Include="Indexers\Exceptions\ApiKeyException.cs" />
|
||||||
<Compile Include="Indexers\Eztv\Eztv.cs" />
|
<Compile Include="Indexers\Eztv\Eztv.cs" />
|
||||||
<Compile Include="Indexers\FetchAndParseRssService.cs" />
|
<Compile Include="Indexers\FetchAndParseRssService.cs" />
|
||||||
<Compile Include="Indexers\IIndexer.cs" />
|
<Compile Include="Indexers\IIndexer.cs" />
|
||||||
<Compile Include="Indexers\IndexerSettingUpdatedEvent.cs" />
|
<Compile Include="Indexers\IndexerSettingUpdatedEvent.cs" />
|
||||||
|
<Compile Include="Indexers\NewznabTestService.cs" />
|
||||||
<Compile Include="Indexers\IndexerWithSetting.cs" />
|
<Compile Include="Indexers\IndexerWithSetting.cs" />
|
||||||
<Compile Include="Indexers\IParseFeed.cs" />
|
<Compile Include="Indexers\IParseFeed.cs" />
|
||||||
|
<Compile Include="Indexers\Newznab\NewznabException.cs" />
|
||||||
|
<Compile Include="Indexers\Newznab\NewznabPreProcessor.cs" />
|
||||||
<Compile Include="Indexers\Newznab\SizeParsingException.cs" />
|
<Compile Include="Indexers\Newznab\SizeParsingException.cs" />
|
||||||
<Compile Include="Indexers\NullSetting.cs" />
|
<Compile Include="Indexers\NullSetting.cs" />
|
||||||
<Compile Include="Indexers\RssSyncCommand.cs" />
|
<Compile Include="Indexers\RssSyncCommand.cs" />
|
||||||
@ -546,6 +550,8 @@
|
|||||||
<Compile Include="Tv\RefreshSeriesService.cs" />
|
<Compile Include="Tv\RefreshSeriesService.cs" />
|
||||||
<Compile Include="Update\Commands\ApplicationUpdateCommand.cs" />
|
<Compile Include="Update\Commands\ApplicationUpdateCommand.cs" />
|
||||||
<Compile Include="Update\InstallUpdateService.cs" />
|
<Compile Include="Update\InstallUpdateService.cs" />
|
||||||
|
<Compile Include="Update\RecentUpdateProvider.cs" />
|
||||||
|
<Compile Include="Update\UpdateChanges.cs" />
|
||||||
<Compile Include="Update\UpdatePackageAvailable.cs" />
|
<Compile Include="Update\UpdatePackageAvailable.cs" />
|
||||||
<Compile Include="Update\UpdatePackageProvider.cs" />
|
<Compile Include="Update\UpdatePackageProvider.cs" />
|
||||||
<Compile Include="Update\UpdatePackage.cs" />
|
<Compile Include="Update\UpdatePackage.cs" />
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Tv.Events;
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
|
||||||
@ -36,8 +37,9 @@ public void RefreshEpisodeInfo(Series series, IEnumerable<Episode> remoteEpisode
|
|||||||
|
|
||||||
var updateList = new List<Episode>();
|
var updateList = new List<Episode>();
|
||||||
var newList = new List<Episode>();
|
var newList = new List<Episode>();
|
||||||
|
var dupeFreeRemoteEpisodes = remoteEpisodes.DistinctBy(m => new { m.SeasonNumber, m.EpisodeNumber }).ToList();
|
||||||
|
|
||||||
foreach (var episode in remoteEpisodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber))
|
foreach (var episode in dupeFreeRemoteEpisodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
32
NzbDrone.Core/Update/RecentUpdateProvider.cs
Normal file
32
NzbDrone.Core/Update/RecentUpdateProvider.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Update
|
||||||
|
{
|
||||||
|
public interface IRecentUpdateProvider
|
||||||
|
{
|
||||||
|
List<UpdatePackage> GetRecentUpdatePackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RecentUpdateProvider : IRecentUpdateProvider
|
||||||
|
{
|
||||||
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
private readonly IUpdatePackageProvider _updatePackageProvider;
|
||||||
|
|
||||||
|
public RecentUpdateProvider(IConfigFileProvider configFileProvider,
|
||||||
|
IUpdatePackageProvider updatePackageProvider)
|
||||||
|
{
|
||||||
|
_configFileProvider = configFileProvider;
|
||||||
|
_updatePackageProvider = updatePackageProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UpdatePackage> GetRecentUpdatePackages()
|
||||||
|
{
|
||||||
|
var branch = _configFileProvider.Branch;
|
||||||
|
return _updatePackageProvider.GetRecentUpdates(branch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
NzbDrone.Core/Update/UpdateChanges.cs
Normal file
17
NzbDrone.Core/Update/UpdateChanges.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Update
|
||||||
|
{
|
||||||
|
public class UpdateChanges
|
||||||
|
{
|
||||||
|
public List<String> New { get; set; }
|
||||||
|
public List<String> Fixed { get; set; }
|
||||||
|
|
||||||
|
public UpdateChanges()
|
||||||
|
{
|
||||||
|
New = new List<String>();
|
||||||
|
Fixed = new List<String>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,5 +13,7 @@ public class UpdatePackage
|
|||||||
public DateTime ReleaseDate { get; set; }
|
public DateTime ReleaseDate { get; set; }
|
||||||
public String FileName { get; set; }
|
public String FileName { get; set; }
|
||||||
public String Url { get; set; }
|
public String Url { get; set; }
|
||||||
|
|
||||||
|
public UpdateChanges Changes { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using RestSharp;
|
using RestSharp;
|
||||||
using NzbDrone.Core.Rest;
|
using NzbDrone.Core.Rest;
|
||||||
@ -8,6 +9,7 @@ namespace NzbDrone.Core.Update
|
|||||||
public interface IUpdatePackageProvider
|
public interface IUpdatePackageProvider
|
||||||
{
|
{
|
||||||
UpdatePackage GetLatestUpdate(string branch, Version currentVersion);
|
UpdatePackage GetLatestUpdate(string branch, Version currentVersion);
|
||||||
|
List<UpdatePackage> GetRecentUpdates(string branch);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpdatePackageProvider : IUpdatePackageProvider
|
public class UpdatePackageProvider : IUpdatePackageProvider
|
||||||
@ -27,5 +29,18 @@ public UpdatePackage GetLatestUpdate(string branch, Version currentVersion)
|
|||||||
|
|
||||||
return update.UpdatePackage;
|
return update.UpdatePackage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<UpdatePackage> GetRecentUpdates(string branch)
|
||||||
|
{
|
||||||
|
var restClient = new RestClient(Services.RootUrl);
|
||||||
|
|
||||||
|
var request = new RestRequest("/v1/update/{branch}/changes");
|
||||||
|
|
||||||
|
request.AddUrlSegment("branch", branch);
|
||||||
|
|
||||||
|
var updates = restClient.ExecuteAndValidate<List<UpdatePackage>>(request);
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,18 +26,21 @@ public void MakeAccessible()
|
|||||||
{
|
{
|
||||||
if (IsFirewallEnabled())
|
if (IsFirewallEnabled())
|
||||||
{
|
{
|
||||||
if (IsNzbDronePortOpen())
|
if (!IsNzbDronePortOpen(_configFileProvider.Port))
|
||||||
{
|
{
|
||||||
_logger.Trace("NzbDrone port is already open, skipping.");
|
_logger.Trace("Opening Port for NzbDrone: {0}", _configFileProvider.Port);
|
||||||
return;
|
OpenFirewallPort(_configFileProvider.Port);
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenFirewallPort(_configFileProvider.Port);
|
if (_configFileProvider.EnableSsl && !IsNzbDronePortOpen(_configFileProvider.SslPort))
|
||||||
|
{
|
||||||
|
_logger.Trace("Opening SSL Port for NzbDrone: {0}", _configFileProvider.SslPort);
|
||||||
|
OpenFirewallPort(_configFileProvider.SslPort);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsNzbDronePortOpen(int port)
|
||||||
private bool IsNzbDronePortOpen()
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -52,7 +55,7 @@ private bool IsNzbDronePortOpen()
|
|||||||
|
|
||||||
foreach (INetFwOpenPort p in ports)
|
foreach (INetFwOpenPort p in ports)
|
||||||
{
|
{
|
||||||
if (p.Port == _configFileProvider.Port)
|
if (p.Port == port)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,8 +66,6 @@ private bool IsNzbDronePortOpen()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void OpenFirewallPort(int portNumber)
|
private void OpenFirewallPort(int portNumber)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -36,7 +36,12 @@ public void Register()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var arguments = String.Format("netsh http add sslcert ipport=0.0.0.0:{0} certhash={1} appid={{{2}}", _configFileProvider.SslPort, _configFileProvider.SslCertHash, APP_ID);
|
var arguments = String.Format("http add sslcert ipport=0.0.0.0:{0} certhash={1} appid={{{2}}}",
|
||||||
|
_configFileProvider.SslPort,
|
||||||
|
_configFileProvider.SslCertHash,
|
||||||
|
APP_ID);
|
||||||
|
|
||||||
|
//TODO: Validate that the cert was added properly, invisible spaces FTL
|
||||||
_netshProvider.Run(arguments);
|
_netshProvider.Run(arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,10 +14,10 @@ namespace NzbDrone.Integration.Test.Client
|
|||||||
{
|
{
|
||||||
private readonly IRestClient _restClient;
|
private readonly IRestClient _restClient;
|
||||||
private readonly string _resource;
|
private readonly string _resource;
|
||||||
|
private readonly string _apiKey;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public ClientBase(IRestClient restClient, string resource = null)
|
public ClientBase(IRestClient restClient, string apiKey, string resource = null)
|
||||||
{
|
{
|
||||||
if (resource == null)
|
if (resource == null)
|
||||||
{
|
{
|
||||||
@ -26,6 +26,7 @@ public ClientBase(IRestClient restClient, string resource = null)
|
|||||||
|
|
||||||
_restClient = restClient;
|
_restClient = restClient;
|
||||||
_resource = resource;
|
_resource = resource;
|
||||||
|
_apiKey = apiKey;
|
||||||
|
|
||||||
_logger = LogManager.GetLogger("REST");
|
_logger = LogManager.GetLogger("REST");
|
||||||
}
|
}
|
||||||
@ -88,10 +89,14 @@ public List<dynamic> InvalidPost(TResource body)
|
|||||||
|
|
||||||
public RestRequest BuildRequest(string command = "")
|
public RestRequest BuildRequest(string command = "")
|
||||||
{
|
{
|
||||||
return new RestRequest(_resource + "/" + command.Trim('/'))
|
var request = new RestRequest(_resource + "/" + command.Trim('/'))
|
||||||
{
|
{
|
||||||
RequestFormat = DataFormat.Json
|
RequestFormat = DataFormat.Json,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
request.AddHeader("Authorization", _apiKey);
|
||||||
|
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T Get<T>(IRestRequest request, HttpStatusCode statusCode = HttpStatusCode.OK) where T : class, new()
|
public T Get<T>(IRestRequest request, HttpStatusCode statusCode = HttpStatusCode.OK) where T : class, new()
|
||||||
|
@ -6,8 +6,8 @@ namespace NzbDrone.Integration.Test.Client
|
|||||||
{
|
{
|
||||||
public class EpisodeClient : ClientBase<EpisodeResource>
|
public class EpisodeClient : ClientBase<EpisodeResource>
|
||||||
{
|
{
|
||||||
public EpisodeClient(IRestClient restClient)
|
public EpisodeClient(IRestClient restClient, string apiKey)
|
||||||
: base(restClient, "episodes")
|
: base(restClient, apiKey, "episodes")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,9 @@ namespace NzbDrone.Integration.Test.Client
|
|||||||
{
|
{
|
||||||
public class IndexerClient : ClientBase<IndexerResource>
|
public class IndexerClient : ClientBase<IndexerResource>
|
||||||
{
|
{
|
||||||
public IndexerClient(IRestClient restClient)
|
public IndexerClient(IRestClient restClient, string apiKey)
|
||||||
: base(restClient)
|
: base(restClient, apiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,12 +5,9 @@ namespace NzbDrone.Integration.Test.Client
|
|||||||
{
|
{
|
||||||
public class ReleaseClient : ClientBase<ReleaseResource>
|
public class ReleaseClient : ClientBase<ReleaseResource>
|
||||||
{
|
{
|
||||||
public ReleaseClient(IRestClient restClient)
|
public ReleaseClient(IRestClient restClient, string apiKey)
|
||||||
: base(restClient)
|
: base(restClient, apiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,8 @@ namespace NzbDrone.Integration.Test.Client
|
|||||||
{
|
{
|
||||||
public class SeriesClient : ClientBase<SeriesResource>
|
public class SeriesClient : ClientBase<SeriesResource>
|
||||||
{
|
{
|
||||||
public SeriesClient(IRestClient restClient)
|
public SeriesClient(IRestClient restClient, string apiKey)
|
||||||
: base(restClient)
|
: base(restClient, apiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,14 +27,11 @@ public SeriesResource Get(string slug, HttpStatusCode statusCode = HttpStatusCod
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class SystemInfoClient : ClientBase<SeriesResource>
|
public class SystemInfoClient : ClientBase<SeriesResource>
|
||||||
{
|
{
|
||||||
public SystemInfoClient(IRestClient restClient)
|
public SystemInfoClient(IRestClient restClient, string apiKey)
|
||||||
: base(restClient)
|
: base(restClient, apiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,22 +47,21 @@ public void SmokeTestSetup()
|
|||||||
_runner = new NzbDroneRunner();
|
_runner = new NzbDroneRunner();
|
||||||
_runner.KillAll();
|
_runner.KillAll();
|
||||||
|
|
||||||
InitRestClients();
|
|
||||||
|
|
||||||
_runner.Start();
|
_runner.Start();
|
||||||
|
InitRestClients();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitRestClients()
|
private void InitRestClients()
|
||||||
{
|
{
|
||||||
RestClient = new RestClient("http://localhost:8989/api");
|
RestClient = new RestClient("http://localhost:8989/api");
|
||||||
Series = new SeriesClient(RestClient);
|
Series = new SeriesClient(RestClient, _runner.ApiKey);
|
||||||
Releases = new ReleaseClient(RestClient);
|
Releases = new ReleaseClient(RestClient, _runner.ApiKey);
|
||||||
RootFolders = new ClientBase<RootFolderResource>(RestClient);
|
RootFolders = new ClientBase<RootFolderResource>(RestClient, _runner.ApiKey);
|
||||||
Commands = new ClientBase<CommandResource>(RestClient);
|
Commands = new ClientBase<CommandResource>(RestClient, _runner.ApiKey);
|
||||||
History = new ClientBase<HistoryResource>(RestClient);
|
History = new ClientBase<HistoryResource>(RestClient, _runner.ApiKey);
|
||||||
Indexers = new IndexerClient(RestClient);
|
Indexers = new IndexerClient(RestClient, _runner.ApiKey);
|
||||||
Episodes = new EpisodeClient(RestClient);
|
Episodes = new EpisodeClient(RestClient, _runner.ApiKey);
|
||||||
NamingConfig = new ClientBase<NamingConfigResource>(RestClient, "config/naming");
|
NamingConfig = new ClientBase<NamingConfigResource>(RestClient, _runner.ApiKey, "config/naming");
|
||||||
}
|
}
|
||||||
|
|
||||||
//[TestFixtureTearDown]
|
//[TestFixtureTearDown]
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Xml.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Processes;
|
using NzbDrone.Common.Processes;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
using RestSharp;
|
using RestSharp;
|
||||||
|
|
||||||
namespace NzbDrone.Integration.Test
|
namespace NzbDrone.Integration.Test
|
||||||
@ -16,16 +19,18 @@ public class NzbDroneRunner
|
|||||||
private readonly IRestClient _restClient;
|
private readonly IRestClient _restClient;
|
||||||
private Process _nzbDroneProcess;
|
private Process _nzbDroneProcess;
|
||||||
|
|
||||||
|
public string AppData { get; private set; }
|
||||||
|
public string ApiKey { get; private set; }
|
||||||
|
|
||||||
public NzbDroneRunner(int port = 8989)
|
public NzbDroneRunner(int port = 8989)
|
||||||
{
|
{
|
||||||
_processProvider = new ProcessProvider();
|
_processProvider = new ProcessProvider();
|
||||||
_restClient = new RestClient("http://localhost:8989/api");
|
_restClient = new RestClient("http://localhost:8989/api");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
AppDate = Path.Combine(Directory.GetCurrentDirectory(), "_intg_" + DateTime.Now.Ticks);
|
AppData = Path.Combine(Directory.GetCurrentDirectory(), "_intg_" + DateTime.Now.Ticks);
|
||||||
|
|
||||||
var nzbdroneConsoleExe = "NzbDrone.Console.exe";
|
var nzbdroneConsoleExe = "NzbDrone.Console.exe";
|
||||||
|
|
||||||
@ -34,7 +39,6 @@ public void Start()
|
|||||||
nzbdroneConsoleExe = "NzbDrone.exe";
|
nzbdroneConsoleExe = "NzbDrone.exe";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (BuildInfo.IsDebug)
|
if (BuildInfo.IsDebug)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -54,8 +58,12 @@ public void Start()
|
|||||||
Assert.Fail("Process has exited");
|
Assert.Fail("Process has exited");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetApiKey();
|
||||||
|
|
||||||
var statusCall = _restClient.Get(new RestRequest("system/status"));
|
var request = new RestRequest("system/status");
|
||||||
|
request.AddHeader("Authorization", ApiKey);
|
||||||
|
|
||||||
|
var statusCall = _restClient.Get(request);
|
||||||
|
|
||||||
if (statusCall.ResponseStatus == ResponseStatus.Completed)
|
if (statusCall.ResponseStatus == ResponseStatus.Completed)
|
||||||
{
|
{
|
||||||
@ -77,7 +85,7 @@ public void KillAll()
|
|||||||
|
|
||||||
private void Start(string outputNzbdroneConsoleExe)
|
private void Start(string outputNzbdroneConsoleExe)
|
||||||
{
|
{
|
||||||
var args = "-nobrowser -data=\"" + AppDate + "\"";
|
var args = "-nobrowser -data=\"" + AppData + "\"";
|
||||||
_nzbDroneProcess = _processProvider.Start(outputNzbdroneConsoleExe, args, OnOutputDataReceived, OnOutputDataReceived);
|
_nzbDroneProcess = _processProvider.Start(outputNzbdroneConsoleExe, args, OnOutputDataReceived, OnOutputDataReceived);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -92,7 +100,16 @@ private void OnOutputDataReceived(string data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetApiKey()
|
||||||
|
{
|
||||||
|
var configFile = Path.Combine(AppData, "config.xml");
|
||||||
|
|
||||||
public string AppDate { get; private set; }
|
if (!String.IsNullOrWhiteSpace(ApiKey)) return;
|
||||||
|
if (!File.Exists(configFile)) return;
|
||||||
|
|
||||||
|
var xDoc = XDocument.Load(configFile);
|
||||||
|
var config = xDoc.Descendants(ConfigFileProvider.CONFIG_ELEMENT_NAME).Single();
|
||||||
|
ApiKey = config.Descendants("ApiKey").Single().Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,25 +1,21 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Debug - Chrome" type="JavascriptDebugSession" factoryName="Remote" singleton="true">
|
<configuration default="false" name="Debug - Chrome" type="JavascriptDebugType" factoryName="JavaScript Debug" singleton="true" uri="http://localhost:8989">
|
||||||
<JSRemoteDebuggerConfigurationSettings>
|
<mapping url="http://localhost:8989/Calendar" local-file="$PROJECT_DIR$/Calendar" />
|
||||||
<option name="engineId" value="chrome" />
|
<mapping url="http://localhost:8989/MainMenuView.js" local-file="$PROJECT_DIR$/MainMenuView.js" />
|
||||||
<option name="fileUrl" value="http://localhost:8989" />
|
<mapping url="http://localhost:8989/Settings" local-file="$PROJECT_DIR$/Settings" />
|
||||||
<mapping url="http://localhost:8989/Calendar" local-file="$PROJECT_DIR$/Calendar" />
|
<mapping url="http://localhost:8989/Upcoming" local-file="$PROJECT_DIR$/Upcoming" />
|
||||||
<mapping url="http://localhost:8989/MainMenuView.js" local-file="$PROJECT_DIR$/MainMenuView.js" />
|
<mapping url="http://localhost:8989/app.js" local-file="$PROJECT_DIR$/app.js" />
|
||||||
<mapping url="http://localhost:8989/Settings" local-file="$PROJECT_DIR$/Settings" />
|
<mapping url="http://localhost:8989/Mixins" local-file="$PROJECT_DIR$/Mixins" />
|
||||||
<mapping url="http://localhost:8989/Upcoming" local-file="$PROJECT_DIR$/Upcoming" />
|
<mapping url="http://localhost:8989/Missing" local-file="$PROJECT_DIR$/Missing" />
|
||||||
<mapping url="http://localhost:8989/app.js" local-file="$PROJECT_DIR$/app.js" />
|
<mapping url="http://localhost:8989/Quality" local-file="$PROJECT_DIR$/Quality" />
|
||||||
<mapping url="http://localhost:8989/Mixins" local-file="$PROJECT_DIR$/Mixins" />
|
<mapping url="http://localhost:8989/Config.js" local-file="$PROJECT_DIR$/Config.js" />
|
||||||
<mapping url="http://localhost:8989/Missing" local-file="$PROJECT_DIR$/Missing" />
|
<mapping url="http://localhost:8989/Shared" local-file="$PROJECT_DIR$/Shared" />
|
||||||
<mapping url="http://localhost:8989/Quality" local-file="$PROJECT_DIR$/Quality" />
|
<mapping url="http://localhost:8989/AddSeries" local-file="$PROJECT_DIR$/AddSeries" />
|
||||||
<mapping url="http://localhost:8989/Config.js" local-file="$PROJECT_DIR$/Config.js" />
|
<mapping url="http://localhost:8989/HeaderView.js" local-file="$PROJECT_DIR$/HeaderView.js" />
|
||||||
<mapping url="http://localhost:8989/Shared" local-file="$PROJECT_DIR$/Shared" />
|
<mapping url="http://localhost:8989" local-file="$PROJECT_DIR$" />
|
||||||
<mapping url="http://localhost:8989/AddSeries" local-file="$PROJECT_DIR$/AddSeries" />
|
<mapping url="http://localhost:8989/Routing.js" local-file="$PROJECT_DIR$/Routing.js" />
|
||||||
<mapping url="http://localhost:8989/HeaderView.js" local-file="$PROJECT_DIR$/HeaderView.js" />
|
<mapping url="http://localhost:8989/Controller.js" local-file="$PROJECT_DIR$/Controller.js" />
|
||||||
<mapping url="http://localhost:8989" local-file="$PROJECT_DIR$" />
|
<mapping url="http://localhost:8989/Series" local-file="$PROJECT_DIR$/Series" />
|
||||||
<mapping url="http://localhost:8989/Routing.js" local-file="$PROJECT_DIR$/Routing.js" />
|
|
||||||
<mapping url="http://localhost:8989/Controller.js" local-file="$PROJECT_DIR$/Controller.js" />
|
|
||||||
<mapping url="http://localhost:8989/Series" local-file="$PROJECT_DIR$/Series" />
|
|
||||||
</JSRemoteDebuggerConfigurationSettings>
|
|
||||||
<RunnerSettings RunnerId="JavascriptDebugRunner" />
|
<RunnerSettings RunnerId="JavascriptDebugRunner" />
|
||||||
<ConfigurationWrapper RunnerId="JavascriptDebugRunner" />
|
<ConfigurationWrapper RunnerId="JavascriptDebugRunner" />
|
||||||
<method />
|
<method />
|
||||||
|
@ -1,25 +1,21 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Debug - Firefox" type="JavascriptDebugSession" factoryName="Remote" singleton="true">
|
<configuration default="false" name="Debug - Firefox" type="JavascriptDebugType" factoryName="JavaScript Debug" singleton="true" engineId="firefox" uri="http://localhost:8989">
|
||||||
<JSRemoteDebuggerConfigurationSettings>
|
<mapping url="http://localhost:8989/Calendar" local-file="$PROJECT_DIR$/Calendar" />
|
||||||
<option name="engineId" value="firefox" />
|
<mapping url="http://localhost:8989/MainMenuView.js" local-file="$PROJECT_DIR$/MainMenuView.js" />
|
||||||
<option name="fileUrl" value="http://localhost:8989" />
|
<mapping url="http://localhost:8989/Settings" local-file="$PROJECT_DIR$/Settings" />
|
||||||
<mapping url="http://localhost:8989/Calendar" local-file="$PROJECT_DIR$/Calendar" />
|
<mapping url="http://localhost:8989/Upcoming" local-file="$PROJECT_DIR$/Upcoming" />
|
||||||
<mapping url="http://localhost:8989/MainMenuView.js" local-file="$PROJECT_DIR$/MainMenuView.js" />
|
<mapping url="http://localhost:8989/app.js" local-file="$PROJECT_DIR$/app.js" />
|
||||||
<mapping url="http://localhost:8989/Settings" local-file="$PROJECT_DIR$/Settings" />
|
<mapping url="http://localhost:8989/Mixins" local-file="$PROJECT_DIR$/Mixins" />
|
||||||
<mapping url="http://localhost:8989/Upcoming" local-file="$PROJECT_DIR$/Upcoming" />
|
<mapping url="http://localhost:8989/Missing" local-file="$PROJECT_DIR$/Missing" />
|
||||||
<mapping url="http://localhost:8989/app.js" local-file="$PROJECT_DIR$/app.js" />
|
<mapping url="http://localhost:8989/Config.js" local-file="$PROJECT_DIR$/Config.js" />
|
||||||
<mapping url="http://localhost:8989/Mixins" local-file="$PROJECT_DIR$/Mixins" />
|
<mapping url="http://localhost:8989/Quality" local-file="$PROJECT_DIR$/Quality" />
|
||||||
<mapping url="http://localhost:8989/Missing" local-file="$PROJECT_DIR$/Missing" />
|
<mapping url="http://localhost:8989/AddSeries" local-file="$PROJECT_DIR$/AddSeries" />
|
||||||
<mapping url="http://localhost:8989/Config.js" local-file="$PROJECT_DIR$/Config.js" />
|
<mapping url="http://localhost:8989/Shared" local-file="$PROJECT_DIR$/Shared" />
|
||||||
<mapping url="http://localhost:8989/Quality" local-file="$PROJECT_DIR$/Quality" />
|
<mapping url="http://localhost:8989/HeaderView.js" local-file="$PROJECT_DIR$/HeaderView.js" />
|
||||||
<mapping url="http://localhost:8989/AddSeries" local-file="$PROJECT_DIR$/AddSeries" />
|
<mapping url="http://localhost:8989" local-file="$PROJECT_DIR$" />
|
||||||
<mapping url="http://localhost:8989/Shared" local-file="$PROJECT_DIR$/Shared" />
|
<mapping url="http://localhost:8989/Routing.js" local-file="$PROJECT_DIR$/Routing.js" />
|
||||||
<mapping url="http://localhost:8989/HeaderView.js" local-file="$PROJECT_DIR$/HeaderView.js" />
|
<mapping url="http://localhost:8989/Controller.js" local-file="$PROJECT_DIR$/Controller.js" />
|
||||||
<mapping url="http://localhost:8989" local-file="$PROJECT_DIR$" />
|
<mapping url="http://localhost:8989/Series" local-file="$PROJECT_DIR$/Series" />
|
||||||
<mapping url="http://localhost:8989/Routing.js" local-file="$PROJECT_DIR$/Routing.js" />
|
|
||||||
<mapping url="http://localhost:8989/Controller.js" local-file="$PROJECT_DIR$/Controller.js" />
|
|
||||||
<mapping url="http://localhost:8989/Series" local-file="$PROJECT_DIR$/Series" />
|
|
||||||
</JSRemoteDebuggerConfigurationSettings>
|
|
||||||
<RunnerSettings RunnerId="JavascriptDebugRunner" />
|
<RunnerSettings RunnerId="JavascriptDebugRunner" />
|
||||||
<ConfigurationWrapper RunnerId="JavascriptDebugRunner" />
|
<ConfigurationWrapper RunnerId="JavascriptDebugRunner" />
|
||||||
<method />
|
<method />
|
||||||
|
@ -4,7 +4,7 @@ define(
|
|||||||
'app',
|
'app',
|
||||||
'marionette',
|
'marionette',
|
||||||
'AddSeries/RootFolders/Layout',
|
'AddSeries/RootFolders/Layout',
|
||||||
'AddSeries/Existing/CollectionView',
|
'AddSeries/Existing/AddExistingSeriesCollectionView',
|
||||||
'AddSeries/AddSeriesView',
|
'AddSeries/AddSeriesView',
|
||||||
'Quality/QualityProfileCollection',
|
'Quality/QualityProfileCollection',
|
||||||
'AddSeries/RootFolders/Collection',
|
'AddSeries/RootFolders/Collection',
|
||||||
@ -15,8 +15,7 @@ define(
|
|||||||
ExistingSeriesCollectionView,
|
ExistingSeriesCollectionView,
|
||||||
AddSeriesView,
|
AddSeriesView,
|
||||||
QualityProfileCollection,
|
QualityProfileCollection,
|
||||||
RootFolderCollection,
|
RootFolderCollection) {
|
||||||
SeriesCollection) {
|
|
||||||
|
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'AddSeries/AddSeriesLayoutTemplate',
|
template: 'AddSeries/AddSeriesLayoutTemplate',
|
||||||
@ -35,8 +34,6 @@ define(
|
|||||||
},
|
},
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
|
|
||||||
SeriesCollection.fetch();
|
|
||||||
QualityProfileCollection.fetch();
|
QualityProfileCollection.fetch();
|
||||||
RootFolderCollection.promise = RootFolderCollection.fetch();
|
RootFolderCollection.promise = RootFolderCollection.fetch();
|
||||||
},
|
},
|
||||||
|
@ -3,14 +3,14 @@ define(
|
|||||||
[
|
[
|
||||||
'app',
|
'app',
|
||||||
'marionette',
|
'marionette',
|
||||||
'AddSeries/Collection',
|
'AddSeries/AddSeriesCollection',
|
||||||
'AddSeries/SearchResultCollectionView',
|
'AddSeries/SearchResultCollectionView',
|
||||||
'AddSeries/NotFoundView',
|
'AddSeries/NotFoundView',
|
||||||
'Shared/LoadingView',
|
'Shared/LoadingView',
|
||||||
'underscore'
|
'underscore'
|
||||||
], function (App, Marionette, AddSeriesCollection, SearchResultCollectionView, NotFoundView, LoadingView, _) {
|
], function (App, Marionette, AddSeriesCollection, SearchResultCollectionView, NotFoundView, LoadingView, _) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'AddSeries/AddSeriesTemplate',
|
template: 'AddSeries/AddSeriesViewTemplate',
|
||||||
|
|
||||||
regions: {
|
regions: {
|
||||||
searchResult: '#search-result'
|
searchResult: '#search-result'
|
||||||
@ -36,12 +36,12 @@ define(
|
|||||||
|
|
||||||
if (this.isExisting) {
|
if (this.isExisting) {
|
||||||
this.className = 'existing-series';
|
this.className = 'existing-series';
|
||||||
this.listenTo(App.vent, App.Events.SeriesAdded, this._onSeriesAdded);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.className = 'new-series';
|
this.className = 'new-series';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.listenTo(App.vent, App.Events.SeriesAdded, this._onSeriesAdded);
|
||||||
this.listenTo(this.collection, 'sync', this._showResults);
|
this.listenTo(this.collection, 'sync', this._showResults);
|
||||||
|
|
||||||
this.resultCollectionView = new SearchResultCollectionView({
|
this.resultCollectionView = new SearchResultCollectionView({
|
||||||
@ -52,21 +52,6 @@ define(
|
|||||||
this.throttledSearch = _.debounce(this.search, 1000, {trailing: true}).bind(this);
|
this.throttledSearch = _.debounce(this.search, 1000, {trailing: true}).bind(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onSeriesAdded: function (options) {
|
|
||||||
if (this.isExisting && options.series.get('path') === this.model.get('folder').path) {
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onLoadMore: function () {
|
|
||||||
var showingAll = this.resultCollectionView.showMore();
|
|
||||||
this.ui.searchBar.show();
|
|
||||||
|
|
||||||
if (showingAll) {
|
|
||||||
this.ui.loadMore.hide();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onRender: function () {
|
onRender: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
@ -77,7 +62,7 @@ define(
|
|||||||
self._abortExistingSearch();
|
self._abortExistingSearch();
|
||||||
self.throttledSearch({
|
self.throttledSearch({
|
||||||
term: self.ui.seriesSearch.val()
|
term: self.ui.seriesSearch.val()
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.isExisting) {
|
if (this.isExisting) {
|
||||||
@ -87,6 +72,7 @@ define(
|
|||||||
|
|
||||||
onShow: function () {
|
onShow: function () {
|
||||||
this.searchResult.show(this.resultCollectionView);
|
this.searchResult.show(this.resultCollectionView);
|
||||||
|
this.ui.seriesSearch.focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
search: function (options) {
|
search: function (options) {
|
||||||
@ -106,6 +92,28 @@ define(
|
|||||||
return this.currentSearchPromise;
|
return this.currentSearchPromise;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onSeriesAdded: function (options) {
|
||||||
|
if (this.isExisting && options.series.get('path') === this.model.get('folder').path) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (!this.isExisting) {
|
||||||
|
this.collection.reset();
|
||||||
|
this.searchResult.show(this.resultCollectionView);
|
||||||
|
this.ui.seriesSearch.val('');
|
||||||
|
this.ui.seriesSearch.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onLoadMore: function () {
|
||||||
|
var showingAll = this.resultCollectionView.showMore();
|
||||||
|
this.ui.searchBar.show();
|
||||||
|
|
||||||
|
if (showingAll) {
|
||||||
|
this.ui.loadMore.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_showResults: function () {
|
_showResults: function () {
|
||||||
if (!this.isClosed) {
|
if (!this.isClosed) {
|
||||||
|
|
||||||
|
@ -29,9 +29,11 @@ define(
|
|||||||
this.addItemView(model, this.getItemView(), index);
|
this.addItemView(model, this.getItemView(), index);
|
||||||
this.children.findByModel(model)
|
this.children.findByModel(model)
|
||||||
.search({term: folderName})
|
.search({term: folderName})
|
||||||
.always((function () {
|
.always(function () {
|
||||||
self._showAndSearch(currentIndex + 1);
|
if (!self.isClosed) {
|
||||||
}));
|
self._showAndSearch(currentIndex + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2,8 +2,7 @@
|
|||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'marionette',
|
'marionette',
|
||||||
'AddSeries/SearchResultView',
|
'AddSeries/SearchResultView'
|
||||||
|
|
||||||
], function (Marionette, SearchResultView) {
|
], function (Marionette, SearchResultView) {
|
||||||
|
|
||||||
return Marionette.CollectionView.extend({
|
return Marionette.CollectionView.extend({
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'app',
|
'app',
|
||||||
|
'underscore',
|
||||||
'marionette',
|
'marionette',
|
||||||
'Quality/QualityProfileCollection',
|
'Quality/QualityProfileCollection',
|
||||||
'AddSeries/RootFolders/Collection',
|
'AddSeries/RootFolders/Collection',
|
||||||
@ -11,7 +12,7 @@ define(
|
|||||||
'Shared/Messenger',
|
'Shared/Messenger',
|
||||||
'Mixins/AsValidatedView',
|
'Mixins/AsValidatedView',
|
||||||
'jquery.dotdotdot'
|
'jquery.dotdotdot'
|
||||||
], function (App, Marionette, QualityProfiles, RootFolders, RootFolderLayout, SeriesCollection, Config, Messenger, AsValidatedView) {
|
], function (App, _, Marionette, QualityProfiles, RootFolders, RootFolderLayout, SeriesCollection, Config, Messenger, AsValidatedView) {
|
||||||
|
|
||||||
var view = Marionette.ItemView.extend({
|
var view = Marionette.ItemView.extend({
|
||||||
|
|
||||||
@ -37,6 +38,9 @@ define(
|
|||||||
throw 'model is required';
|
throw 'model is required';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.templateHelpers = {};
|
||||||
|
this._configureTemplateHelpers();
|
||||||
|
|
||||||
this.listenTo(App.vent, Config.Events.ConfigUpdatedEvent, this._onConfigUpdated);
|
this.listenTo(App.vent, Config.Events.ConfigUpdatedEvent, this._onConfigUpdated);
|
||||||
this.listenTo(this.model, 'change', this.render);
|
this.listenTo(this.model, 'change', this.render);
|
||||||
this.listenTo(RootFolders, 'all', this.render);
|
this.listenTo(RootFolders, 'all', this.render);
|
||||||
@ -71,22 +75,18 @@ define(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
serializeData: function () {
|
_configureTemplateHelpers: function () {
|
||||||
var data = this.model.toJSON();
|
|
||||||
|
|
||||||
var existingSeries = SeriesCollection.where({tvdbId: this.model.get('tvdbId')});
|
var existingSeries = SeriesCollection.where({tvdbId: this.model.get('tvdbId')});
|
||||||
|
|
||||||
if (existingSeries.length > 0) {
|
if (existingSeries.length > 0) {
|
||||||
data.existing = existingSeries[0].toJSON();
|
this.templateHelpers.existing = existingSeries[0].toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
data.qualityProfiles = QualityProfiles.toJSON();
|
this.templateHelpers.qualityProfiles = QualityProfiles.toJSON();
|
||||||
|
|
||||||
if (!data.isExisting) {
|
if (!this.model.get('isExisting')) {
|
||||||
data.rootFolders = RootFolders.toJSON();
|
this.templateHelpers.rootFolders = RootFolders.toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onConfigUpdated: function (options) {
|
_onConfigUpdated: function (options) {
|
||||||
@ -134,17 +134,23 @@ define(
|
|||||||
|
|
||||||
SeriesCollection.add(this.model);
|
SeriesCollection.add(this.model);
|
||||||
|
|
||||||
this.model.save().done(function () {
|
|
||||||
|
var promise = this.model.save();
|
||||||
|
|
||||||
|
promise.done(function () {
|
||||||
self.close();
|
self.close();
|
||||||
icon.removeClass('icon-spin icon-spinner disabled').addClass('icon-search');
|
icon.removeClass('icon-spin icon-spinner disabled').addClass('icon-search');
|
||||||
|
|
||||||
Messenger.show({
|
Messenger.show({
|
||||||
message: 'Added: ' + self.model.get('title')
|
message: 'Added: ' + self.model.get('title')
|
||||||
});
|
});
|
||||||
|
|
||||||
App.vent.trigger(App.Events.SeriesAdded, { series: self.model });
|
App.vent.trigger(App.Events.SeriesAdded, { series: self.model });
|
||||||
}).fail(function () {
|
});
|
||||||
icon.removeClass('icon-spin icon-spinner disabled').addClass('icon-search');
|
|
||||||
});
|
promise.fail(function () {
|
||||||
|
icon.removeClass('icon-spin icon-spinner disabled').addClass('icon-search');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
69
UI/Cells/Header/QualityHeaderCell.js
Normal file
69
UI/Cells/Header/QualityHeaderCell.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'backgrid',
|
||||||
|
'Shared/Grid/HeaderCell'
|
||||||
|
], function (Backgrid, NzbDroneHeaderCell) {
|
||||||
|
|
||||||
|
Backgrid.QualityHeaderCell = NzbDroneHeaderCell.extend({
|
||||||
|
events: {
|
||||||
|
'click': 'onClick'
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var columnName = this.column.get('name');
|
||||||
|
|
||||||
|
if (this.column.get('sortable')) {
|
||||||
|
if (this.direction() === 'ascending') {
|
||||||
|
this.sort(columnName, 'descending', function (left, right) {
|
||||||
|
var leftVal = left.get(columnName);
|
||||||
|
var rightVal = right.get(columnName);
|
||||||
|
|
||||||
|
return self._comparator(leftVal, rightVal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.sort(columnName, 'ascending', function (left, right) {
|
||||||
|
var leftVal = left.get(columnName);
|
||||||
|
var rightVal = right.get(columnName);
|
||||||
|
|
||||||
|
return self._comparator(rightVal, leftVal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_comparator: function (leftVal, rightVal) {
|
||||||
|
var leftWeight = leftVal.quality.weight;
|
||||||
|
var rightWeight = rightVal.quality.weight;
|
||||||
|
|
||||||
|
if (!leftWeight && !rightWeight) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!leftWeight) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rightWeight) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftWeight === rightWeight) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftWeight > rightWeight) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Backgrid.QualityHeaderCell;
|
||||||
|
});
|
@ -1,56 +1,66 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
|
'app',
|
||||||
'Commands/CommandModel',
|
'Commands/CommandModel',
|
||||||
'Commands/CommandCollection',
|
'Commands/CommandCollection',
|
||||||
'underscore',
|
'underscore',
|
||||||
'jQuery/jquery.spin'
|
'jQuery/jquery.spin'
|
||||||
], function (CommandModel, CommandCollection, _) {
|
], function (App, CommandModel, CommandCollection, _) {
|
||||||
|
|
||||||
return{
|
var singleton = function () {
|
||||||
|
|
||||||
Execute: function (name, properties) {
|
return {
|
||||||
|
|
||||||
var attr = _.extend({name: name.toLocaleLowerCase()}, properties);
|
Execute: function (name, properties) {
|
||||||
|
|
||||||
var commandModel = new CommandModel(attr);
|
var attr = _.extend({name: name.toLocaleLowerCase()}, properties);
|
||||||
|
|
||||||
return commandModel.save().success(function () {
|
var commandModel = new CommandModel(attr);
|
||||||
CommandCollection.add(commandModel);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
bindToCommand: function (options) {
|
return commandModel.save().success(function () {
|
||||||
|
CommandCollection.add(commandModel);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
var self = this;
|
bindToCommand: function (options) {
|
||||||
|
|
||||||
var existingCommand = CommandCollection.findCommand(options.command);
|
var self = this;
|
||||||
|
|
||||||
if (existingCommand) {
|
var existingCommand = CommandCollection.findCommand(options.command);
|
||||||
this._bindToCommandModel.call(this, existingCommand, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
CommandCollection.bind('add sync', function (model) {
|
if (existingCommand) {
|
||||||
if (model.isSameCommand(options.command)) {
|
this._bindToCommandModel.call(this, existingCommand, options);
|
||||||
self._bindToCommandModel.call(self, model, options);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_bindToCommandModel: function bindToCommand(model, options) {
|
CommandCollection.bind('add sync', function (model) {
|
||||||
|
if (model.isSameCommand(options.command)) {
|
||||||
|
self._bindToCommandModel.call(self, model, options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
if (!model.isActive()) {
|
_bindToCommandModel: function bindToCommand(model, options) {
|
||||||
options.element.stopSpin();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
model.bind('change:state', function (model) {
|
|
||||||
if (!model.isActive()) {
|
if (!model.isActive()) {
|
||||||
options.element.stopSpin();
|
options.element.stopSpin();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
options.element.startSpin();
|
model.bind('change:state', function (model) {
|
||||||
}
|
if (!model.isActive()) {
|
||||||
}
|
options.element.stopSpin();
|
||||||
|
|
||||||
|
if (model.isComplete()) {
|
||||||
|
App.vent.trigger(App.Events.CommandComplete, { command: model, model: options.model });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
options.element.startSpin();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return singleton();
|
||||||
});
|
});
|
||||||
|
@ -11,13 +11,9 @@ define(
|
|||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
isActive: function () {
|
|
||||||
return this.get('state') !== 'completed' && this.get('state') !== 'failed';
|
|
||||||
},
|
|
||||||
|
|
||||||
isSameCommand: function (command) {
|
isSameCommand: function (command) {
|
||||||
|
|
||||||
if (command.name.toLocaleLowerCase() != this.get('name').toLocaleLowerCase()) {
|
if (command.name.toLocaleLowerCase() !== this.get('name').toLocaleLowerCase()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,6 +24,14 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
isActive: function () {
|
||||||
|
return this.get('state') !== 'completed' && this.get('state') !== 'failed';
|
||||||
|
},
|
||||||
|
|
||||||
|
isComplete: function () {
|
||||||
|
return this.get('state') === 'completed';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -12,6 +12,10 @@ define(
|
|||||||
DefaultRootFolderId: 'DefaultRootFolderId'
|
DefaultRootFolderId: 'DefaultRootFolderId'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getValueBoolean: function (key, defaultValue) {
|
||||||
|
return this.getValue(key, defaultValue) === 'true';
|
||||||
|
},
|
||||||
|
|
||||||
getValue: function (key, defaultValue) {
|
getValue: function (key, defaultValue) {
|
||||||
|
|
||||||
var storeValue = localStorage.getItem(key);
|
var storeValue = localStorage.getItem(key);
|
||||||
@ -35,6 +39,5 @@ define(
|
|||||||
App.vent.trigger(this.Events.ConfigUpdatedEvent, {key: key, value: value});
|
App.vent.trigger(this.Events.ConfigUpdatedEvent, {key: key, value: value});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
.slide-button {
|
.slide-button {
|
||||||
.buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight);
|
.buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight);
|
||||||
|
|
||||||
&.btn-danger {
|
&.btn-danger, &.btn-warning {
|
||||||
.buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight);
|
.buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -16,5 +16,9 @@
|
|||||||
&.btn-danger {
|
&.btn-danger {
|
||||||
.buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight);
|
.buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.btn-warning {
|
||||||
|
.buttonBackground(@btnWarningBackground, @btnWarningBackgroundHighlight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -162,6 +162,10 @@ footer {
|
|||||||
color : @successText;
|
color : @successText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-warning {
|
||||||
|
color : @warningText;
|
||||||
|
}
|
||||||
|
|
||||||
.status-danger {
|
.status-danger {
|
||||||
color : @errorText;
|
color : @errorText;
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,12 @@ define(
|
|||||||
'Logs/Files/Layout',
|
'Logs/Files/Layout',
|
||||||
'Release/Layout',
|
'Release/Layout',
|
||||||
'System/Layout',
|
'System/Layout',
|
||||||
'SeasonPass/Layout',
|
'SeasonPass/SeasonPassLayout',
|
||||||
|
'Update/UpdateLayout',
|
||||||
'Shared/NotFoundView',
|
'Shared/NotFoundView',
|
||||||
'Shared/Modal/Region'
|
'Shared/Modal/Region'
|
||||||
], function (App, Marionette, HistoryLayout, SettingsLayout, AddSeriesLayout, SeriesIndexLayout, SeriesDetailsLayout, SeriesCollection, MissingLayout, CalendarLayout,
|
], function (App, Marionette, HistoryLayout, SettingsLayout, AddSeriesLayout, SeriesIndexLayout, SeriesDetailsLayout, SeriesCollection, MissingLayout, CalendarLayout,
|
||||||
LogsLayout, LogFileLayout, ReleaseLayout, SystemLayout, SeasonPassLayout, NotFoundView) {
|
LogsLayout, LogFileLayout, ReleaseLayout, SystemLayout, SeasonPassLayout, UpdateLayout, NotFoundView) {
|
||||||
return Marionette.Controller.extend({
|
return Marionette.Controller.extend({
|
||||||
|
|
||||||
series: function () {
|
series: function () {
|
||||||
@ -94,6 +95,11 @@ define(
|
|||||||
App.mainRegion.show(new SeasonPassLayout());
|
App.mainRegion.show(new SeasonPassLayout());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
update: function () {
|
||||||
|
this._setTitle('Updates');
|
||||||
|
App.mainRegion.show(new UpdateLayout());
|
||||||
|
},
|
||||||
|
|
||||||
notFound: function () {
|
notFound: function () {
|
||||||
this._setTitle('Not Found');
|
this._setTitle('Not Found');
|
||||||
App.mainRegion.show(new NotFoundView(this));
|
App.mainRegion.show(new NotFoundView(this));
|
||||||
|
@ -6,9 +6,10 @@ define(
|
|||||||
'Cells/FileSizeCell',
|
'Cells/FileSizeCell',
|
||||||
'Cells/QualityCell',
|
'Cells/QualityCell',
|
||||||
'Cells/ApprovalStatusCell',
|
'Cells/ApprovalStatusCell',
|
||||||
'Release/DownloadReportCell'
|
'Release/DownloadReportCell',
|
||||||
|
'Cells/Header/QualityHeaderCell'
|
||||||
|
|
||||||
], function (Marionette, Backgrid, FileSizeCell, QualityCell, ApprovalStatusCell, DownloadReportCell) {
|
], function (Marionette, Backgrid, FileSizeCell, QualityCell, ApprovalStatusCell, DownloadReportCell, QualityHeaderCell) {
|
||||||
|
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'Episode/Search/ManualLayoutTemplate',
|
template: 'Episode/Search/ManualLayoutTemplate',
|
||||||
@ -44,10 +45,11 @@ define(
|
|||||||
cell : FileSizeCell
|
cell : FileSizeCell
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'quality',
|
name : 'quality',
|
||||||
label : 'Quality',
|
label : 'Quality',
|
||||||
sortable: true,
|
sortable : true,
|
||||||
cell : QualityCell
|
cell : QualityCell,
|
||||||
|
headerCell: QualityHeaderCell
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -57,10 +57,10 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (seasonCount === 1) {
|
if (seasonCount === 1) {
|
||||||
return new Handlebars.SafeString('<span class="label label-info">{0} Season</span>'.format(seasonCount))
|
return new Handlebars.SafeString('<span class="label label-info">{0} Season</span>'.format(seasonCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Handlebars.SafeString('<span class="label label-info">{0} Seasons</span>'.format(seasonCount))
|
return new Handlebars.SafeString('<span class="label label-info">{0} Seasons</span>'.format(seasonCount));
|
||||||
});
|
});
|
||||||
|
|
||||||
Handlebars.registerHelper('titleWithYear', function () {
|
Handlebars.registerHelper('titleWithYear', function () {
|
||||||
|
18
UI/Handlebars/Helpers/Version.js
Normal file
18
UI/Handlebars/Helpers/Version.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'handlebars'
|
||||||
|
], function (Handlebars) {
|
||||||
|
|
||||||
|
Handlebars.registerHelper('currentVersion', function (version) {
|
||||||
|
var currentVersion = window.NzbDrone.ServerStatus.version;
|
||||||
|
|
||||||
|
if (currentVersion === version)
|
||||||
|
{
|
||||||
|
return new Handlebars.SafeString('<i class="icon-ok" title="Installed"></i>');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
});
|
@ -9,6 +9,7 @@ define(
|
|||||||
'Handlebars/Helpers/Episode',
|
'Handlebars/Helpers/Episode',
|
||||||
'Handlebars/Helpers/Series',
|
'Handlebars/Helpers/Series',
|
||||||
'Handlebars/Helpers/Quality',
|
'Handlebars/Helpers/Quality',
|
||||||
|
'Handlebars/Helpers/Version',
|
||||||
'Handlebars/Handlebars.Debug'
|
'Handlebars/Handlebars.Debug'
|
||||||
], function (Templates) {
|
], function (Templates) {
|
||||||
return function () {
|
return function () {
|
||||||
|
@ -60,9 +60,10 @@ define(
|
|||||||
cell : EpisodeTitleCell
|
cell : EpisodeTitleCell
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'quality',
|
name : 'quality',
|
||||||
label: 'Quality',
|
label : 'Quality',
|
||||||
cell : QualityCell
|
cell : QualityCell,
|
||||||
|
sortable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'date',
|
name : 'date',
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
var filename = a.pathname.split('/').pop();
|
var filename = a.pathname.split('/').pop();
|
||||||
|
|
||||||
//Suppress Firefox debug errors when console window is closed
|
//Suppress Firefox debug errors when console window is closed
|
||||||
if (filename.toLowerCase() === 'markupview.jsm') {
|
if (filename.toLowerCase() === 'markupview.jsm' || filename.toLowerCase() === 'markup-view.js') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,9 +20,12 @@ define(function () {
|
|||||||
|
|
||||||
delete xhr.data;
|
delete xhr.data;
|
||||||
}
|
}
|
||||||
|
if (xhr) {
|
||||||
|
xhr.headers = xhr.headers || {};
|
||||||
|
xhr.headers['Authorization'] = window.NzbDrone.ApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
return original.apply(this, arguments);
|
return original.apply(this, arguments);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
@ -50,7 +50,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=KRTE52U3XJDSQ" target="_blank">
|
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HGGGM7JT5YVSS" target="_blank">
|
||||||
<i class="icon-nd-donate"></i>
|
<i class="icon-nd-donate"></i>
|
||||||
<br>
|
<br>
|
||||||
Donate
|
Donate
|
||||||
|
@ -4,12 +4,21 @@ define(
|
|||||||
'app',
|
'app',
|
||||||
'Series/SeriesCollection'
|
'Series/SeriesCollection'
|
||||||
], function (App, SeriesCollection) {
|
], function (App, SeriesCollection) {
|
||||||
|
$(document).on('keydown', function (e){
|
||||||
|
if ($(e.target).is('input')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 84) {
|
||||||
|
$('.x-series-search').focus();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$.fn.bindSearch = function () {
|
$.fn.bindSearch = function () {
|
||||||
$(this).typeahead({
|
$(this).typeahead({
|
||||||
source : function () {
|
source : function () {
|
||||||
return SeriesCollection.map(function (model) {
|
return SeriesCollection.pluck('title');
|
||||||
return model.get('title');
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
sorter: function (items) {
|
sorter: function (items) {
|
||||||
@ -17,9 +26,7 @@ define(
|
|||||||
},
|
},
|
||||||
|
|
||||||
updater: function (item) {
|
updater: function (item) {
|
||||||
var series = SeriesCollection.find(function (model) {
|
var series = SeriesCollection.findWhere({ title: item });
|
||||||
return model.get('title') === item;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$element.blur();
|
this.$element.blur();
|
||||||
App.Router.navigate('/series/{0}'.format(series.get('titleSlug')), { trigger: true });
|
App.Router.navigate('/series/{0}'.format(series.get('titleSlug')), { trigger: true });
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'Release/Model',
|
'backbone',
|
||||||
'backbone.pageable'
|
'Release/Model'
|
||||||
], function (ReleaseModel, PagableCollection) {
|
], function (Backbone, ReleaseModel) {
|
||||||
return PagableCollection.extend({
|
return Backbone.Collection.extend({
|
||||||
url : window.NzbDrone.ApiRoot + '/release',
|
url : window.NzbDrone.ApiRoot + '/release',
|
||||||
model: ReleaseModel,
|
model: ReleaseModel,
|
||||||
|
|
||||||
mode: 'client',
|
|
||||||
|
|
||||||
state: {
|
state: {
|
||||||
pageSize: 2000
|
pageSize: 2000
|
||||||
},
|
},
|
||||||
|
@ -31,6 +31,7 @@ require(
|
|||||||
'rss' : 'rss',
|
'rss' : 'rss',
|
||||||
'system' : 'system',
|
'system' : 'system',
|
||||||
'seasonpass' : 'seasonPass',
|
'seasonpass' : 'seasonPass',
|
||||||
|
'update' : 'update',
|
||||||
':whatever' : 'notFound'
|
':whatever' : 'notFound'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -12,7 +12,7 @@ define(
|
|||||||
SeriesCollectionView,
|
SeriesCollectionView,
|
||||||
LoadingView) {
|
LoadingView) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'SeasonPass/LayoutTemplate',
|
template: 'SeasonPass/SeasonPassLayoutTemplate',
|
||||||
|
|
||||||
regions: {
|
regions: {
|
||||||
series: '#x-series'
|
series: '#x-series'
|
@ -1,24 +1,28 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
|
'underscore',
|
||||||
'marionette',
|
'marionette',
|
||||||
'backgrid',
|
'backgrid',
|
||||||
'Series/SeasonCollection'
|
'Series/SeasonCollection'
|
||||||
], function (Marionette, Backgrid, SeasonCollection) {
|
], function (_, Marionette, Backgrid, SeasonCollection) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'SeasonPass/SeriesLayoutTemplate',
|
template: 'SeasonPass/SeriesLayoutTemplate',
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
seasonSelect: '.x-season-select',
|
seasonSelect : '.x-season-select',
|
||||||
expander : '.x-expander',
|
expander : '.x-expander',
|
||||||
seasonGrid : '.x-season-grid'
|
seasonGrid : '.x-season-grid',
|
||||||
|
seriesMonitored: '.x-series-monitored'
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'change .x-season-select': '_seasonSelected',
|
'change .x-season-select' : '_seasonSelected',
|
||||||
'click .x-expander' : '_expand',
|
'click .x-expander' : '_expand',
|
||||||
'click .x-latest' : '_latest',
|
'click .x-latest' : '_latest',
|
||||||
'click .x-monitored' : '_toggleSeasonMonitored'
|
'click .x-all' : '_all',
|
||||||
|
'click .x-monitored' : '_toggleSeasonMonitored',
|
||||||
|
'click .x-series-monitored': '_toggleSeriesMonitored'
|
||||||
},
|
},
|
||||||
|
|
||||||
regions: {
|
regions: {
|
||||||
@ -26,6 +30,7 @@ define(
|
|||||||
},
|
},
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
|
this.listenTo(this.model, 'sync', this._setSeriesMonitoredState);
|
||||||
this.seasonCollection = new SeasonCollection(this.model.get('seasons'));
|
this.seasonCollection = new SeasonCollection(this.model.get('seasons'));
|
||||||
this.expanded = false;
|
this.expanded = false;
|
||||||
},
|
},
|
||||||
@ -36,16 +41,17 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._setExpanderIcon();
|
this._setExpanderIcon();
|
||||||
|
this._setSeriesMonitoredState();
|
||||||
},
|
},
|
||||||
|
|
||||||
_seasonSelected: function () {
|
_seasonSelected: function () {
|
||||||
var seasonNumber = parseInt(this.ui.seasonSelect.val());
|
var seasonNumber = parseInt(this.ui.seasonSelect.val());
|
||||||
|
|
||||||
if (seasonNumber == -1 || isNaN(seasonNumber)) {
|
if (seasonNumber === -1 || isNaN(seasonNumber)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._setMonitored(seasonNumber)
|
this._setSeasonMonitored(seasonNumber);
|
||||||
},
|
},
|
||||||
|
|
||||||
_expand: function () {
|
_expand: function () {
|
||||||
@ -79,10 +85,16 @@ define(
|
|||||||
return s.seasonNumber;
|
return s.seasonNumber;
|
||||||
});
|
});
|
||||||
|
|
||||||
this._setMonitored(season.seasonNumber);
|
this._setSeasonMonitored(season.seasonNumber);
|
||||||
},
|
},
|
||||||
|
|
||||||
_setMonitored: function (seasonNumber) {
|
_all: function () {
|
||||||
|
var minSeasonNotZero = _.min(_.reject(this.model.get('seasons'), { seasonNumber: 0 }), 'seasonNumber');
|
||||||
|
|
||||||
|
this._setSeasonMonitored(minSeasonNotZero.seasonNumber);
|
||||||
|
},
|
||||||
|
|
||||||
|
_setSeasonMonitored: function (seasonNumber) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.model.setSeasonPass(seasonNumber);
|
this.model.setSeasonPass(seasonNumber);
|
||||||
@ -118,6 +130,29 @@ define(
|
|||||||
|
|
||||||
_afterToggleSeasonMonitored: function () {
|
_afterToggleSeasonMonitored: function () {
|
||||||
this.render();
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
_setSeriesMonitoredState: function () {
|
||||||
|
var monitored = this.model.get('monitored');
|
||||||
|
|
||||||
|
this.ui.seriesMonitored.removeAttr('data-idle-icon');
|
||||||
|
|
||||||
|
if (monitored) {
|
||||||
|
this.ui.seriesMonitored.addClass('icon-nd-monitored');
|
||||||
|
this.ui.seriesMonitored.removeClass('icon-nd-unmonitored');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.ui.seriesMonitored.addClass('icon-nd-unmonitored');
|
||||||
|
this.ui.seriesMonitored.removeClass('icon-nd-monitored');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_toggleSeriesMonitored: function (e) {
|
||||||
|
var savePromise = this.model.save('monitored', !this.model.get('monitored'), {
|
||||||
|
wait: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ui.seriesMonitored.spinForPromise(savePromise);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<div class="seasonpass-series">
|
<div class="seasonpass-series">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="span11">
|
<div class="span12">
|
||||||
<i class="icon-chevron-right x-expander expander pull-left"/>
|
<i class="icon-chevron-right x-expander expander pull-left"/>
|
||||||
|
<i class="x-series-monitored series-monitor-toggle pull-left" title="Toggle monitored state for entire series"/>
|
||||||
<span class="title span5">
|
<span class="title span5">
|
||||||
<a href="{{route}}">
|
<a href="{{route}}">
|
||||||
{{title}}
|
{{title}}
|
||||||
@ -26,10 +26,20 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<button class="btn x-latest">Latest</button>
|
<span class="season-pass-button">
|
||||||
|
<button class="btn x-latest">Latest</button>
|
||||||
|
|
||||||
<span class="help-inline">
|
<span class="help-inline">
|
||||||
<i class="icon-question-sign" title="Will quickly select the latest season as first monitored"/>
|
<i class="icon-question-sign" title="Will quickly select the latest season as first monitored"/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="season-pass-button">
|
||||||
|
<button class="btn x-all">All</button>
|
||||||
|
|
||||||
|
<span class="help-inline">
|
||||||
|
<i class="icon-question-sign" title="Will quickly select all seasons except for specials to be monitored"/>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,13 +9,17 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if_eq episodeCount compare=0}}
|
{{#if_eq episodeCount compare=0}}
|
||||||
<i class="icon-nd-status season-status status-primary" title="No aired episodes"/>
|
{{#if monitored}}
|
||||||
|
<i class="icon-nd-status season-status status-primary" title="No aired episodes"/>
|
||||||
|
{{else}}
|
||||||
|
<i class="icon-nd-status season-status status-warning" title="Season is not monitored"/>
|
||||||
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if_eq percentOfEpisodes compare=100}}
|
{{#if_eq percentOfEpisodes compare=100}}
|
||||||
<i class="icon-nd-status season-status status-success" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/>
|
<i class="icon-nd-status season-status status-success" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/>
|
||||||
{{else}}
|
{{else}}
|
||||||
<i class="icon-nd-status season-status status-danger" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/>
|
<i class="icon-nd-status season-status status-danger" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded"/>
|
||||||
{{/if_eq}}
|
{{/if_eq}}
|
||||||
{{/if_eq}}
|
{{/if_eq}}
|
||||||
|
|
||||||
<span class="season-actions pull-right">
|
<span class="season-actions pull-right">
|
||||||
|
@ -44,6 +44,8 @@ define(
|
|||||||
this.listenTo(this.model, 'change:monitored', this._setMonitoredState);
|
this.listenTo(this.model, 'change:monitored', this._setMonitoredState);
|
||||||
this.listenTo(App.vent, App.Events.SeriesDeleted, this._onSeriesDeleted);
|
this.listenTo(App.vent, App.Events.SeriesDeleted, this._onSeriesDeleted);
|
||||||
this.listenTo(App.vent, App.Events.SeasonRenamed, this._onSeasonRenamed);
|
this.listenTo(App.vent, App.Events.SeasonRenamed, this._onSeasonRenamed);
|
||||||
|
|
||||||
|
App.vent.on(App.Events.CommandComplete, this._commandComplete, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
onShow: function () {
|
onShow: function () {
|
||||||
@ -195,6 +197,16 @@ define(
|
|||||||
if (this.model.get('id') === event.series.get('id')) {
|
if (this.model.get('id') === event.series.get('id')) {
|
||||||
this.episodeFileCollection.fetch();
|
this.episodeFileCollection.fetch();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_commandComplete: function (options) {
|
||||||
|
if (options.command.get('name') === 'refreshseries' || options.command.get('name') === 'renameseries') {
|
||||||
|
if (options.command.get('seriesId') === this.model.get('id')) {
|
||||||
|
this._showSeasons();
|
||||||
|
this._setMonitoredState();
|
||||||
|
this._showInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="span11">
|
<div class="span11">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h1>
|
<h1>
|
||||||
<i class="x-monitored clickable series-monitor-toggle" title="Toggle monitored state for entire series"/>
|
<i class="x-monitored" title="Toggle monitored state for entire series"/>
|
||||||
{{title}}
|
{{title}}
|
||||||
<div class="series-actions pull-right">
|
<div class="series-actions pull-right">
|
||||||
<div class="x-refresh">
|
<div class="x-refresh">
|
||||||
|
@ -274,3 +274,16 @@
|
|||||||
font-size : 16px;
|
font-size : 16px;
|
||||||
vertical-align : middle !important;
|
vertical-align : middle !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.seasonpass-series {
|
||||||
|
.season-pass-button {
|
||||||
|
display: inline-block;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-monitor-toggle {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
window.NzbDrone = {};
|
|
||||||
window.NzbDrone.ApiRoot = '/api';
|
window.NzbDrone.ApiRoot = '/api';
|
||||||
|
|
||||||
var statusText = $.ajax({
|
var statusText = $.ajax({
|
||||||
type : 'GET',
|
type : 'GET',
|
||||||
url : window.NzbDrone.ApiRoot + '/system/status',
|
url : window.NzbDrone.ApiRoot + '/system/status',
|
||||||
async: false
|
async: false,
|
||||||
|
headers: {
|
||||||
|
Authorization: window.NzbDrone.ApiKey
|
||||||
|
}
|
||||||
}).responseText;
|
}).responseText;
|
||||||
|
|
||||||
window.NzbDrone.ServerStatus = JSON.parse(statusText);
|
window.NzbDrone.ServerStatus = JSON.parse(statusText);
|
||||||
|
@ -7,11 +7,49 @@
|
|||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="number" placeholder="8989" name="port"/>
|
<input type="number" placeholder="8989" name="port"/>
|
||||||
<span>
|
<span>
|
||||||
<i class="icon-nd-form-warning" title="Requires restart to take effect"/>
|
<i class="icon-nd-form-warning" title="Requires restart to take effect"/>
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group advanced-setting">
|
||||||
|
<label class="control-label">Enable SSL</label>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox toggle well">
|
||||||
|
<input type="checkbox" name="enableSsl" class="x-ssl"/>
|
||||||
|
|
||||||
|
<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-warning" title="Requires restart running as administrator to take effect"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="x-ssl-options">
|
||||||
|
<div class="control-group advanced-setting">
|
||||||
|
<label class="control-label">SSL Port Number</label>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<input type="number" placeholder="8989" name="sslPort"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group advanced-setting">
|
||||||
|
<label class="control-label">SSL Cert Hash</label>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" name="sslCertHash"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
@ -29,9 +67,9 @@
|
|||||||
<div class="btn btn-primary slide-button"/>
|
<div class="btn btn-primary slide-button"/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<span class="help-inline-checkbox">
|
<span class="help-inline-checkbox">
|
||||||
<i class="icon-question-sign" title="Open a web browser and navigate to NzbDrone homepage on app start. Has no effect if installed as a windows service"/>
|
<i class="icon-nd-form-info" title="Open a web browser and navigate to NzbDrone homepage on app start. Has no effect if installed as a windows service"/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@ -51,7 +89,7 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<span class="help-inline-checkbox">
|
<span class="help-inline-checkbox">
|
||||||
<i class="icon-question-sign" title="Require Username and Password to access Nzbdrone"/>
|
<i class="icon-nd-form-info" title="Require Username and Password to access Nzbdrone"/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -91,8 +129,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
{{#unless_eq branch compare="master"}}
|
<fieldset class="advanced-setting">
|
||||||
<fieldset>
|
|
||||||
<legend>Development</legend>
|
<legend>Development</legend>
|
||||||
<div class="alert">
|
<div class="alert">
|
||||||
<i class="icon-nd-warning"></i>
|
<i class="icon-nd-warning"></i>
|
||||||
@ -106,5 +143,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{/unless_eq}}
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,38 +5,56 @@ define(
|
|||||||
'Mixins/AsModelBoundView'
|
'Mixins/AsModelBoundView'
|
||||||
], function (Marionette, AsModelBoundView) {
|
], function (Marionette, AsModelBoundView) {
|
||||||
var view = Marionette.ItemView.extend({
|
var view = Marionette.ItemView.extend({
|
||||||
template: 'Settings/General/GeneralTemplate',
|
template: 'Settings/General/GeneralTemplate',
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'change .x-auth': '_setAuthOptionsVisibility'
|
'change .x-auth': '_setAuthOptionsVisibility',
|
||||||
},
|
'change .x-ssl': '_setSslOptionsVisibility'
|
||||||
|
},
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
authToggle : '.x-auth',
|
authToggle : '.x-auth',
|
||||||
authOptions: '.x-auth-options'
|
authOptions: '.x-auth-options',
|
||||||
},
|
sslToggle : '.x-ssl',
|
||||||
|
sslOptions: '.x-ssl-options'
|
||||||
|
},
|
||||||
|
|
||||||
|
onRender: function(){
|
||||||
onRender: function(){
|
if(!this.ui.authToggle.prop('checked')){
|
||||||
if(!this.ui.authToggle.prop('checked')){
|
this.ui.authOptions.hide();
|
||||||
this.ui.authOptions.hide();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_setAuthOptionsVisibility: function () {
|
|
||||||
|
|
||||||
var showAuthOptions = this.ui.authToggle.prop('checked');
|
|
||||||
|
|
||||||
if (showAuthOptions) {
|
|
||||||
this.ui.authOptions.slideDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
this.ui.authOptions.slideUp();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
if(!this.ui.sslToggle.prop('checked')){
|
||||||
|
this.ui.sslOptions.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_setAuthOptionsVisibility: function () {
|
||||||
|
|
||||||
|
var showAuthOptions = this.ui.authToggle.prop('checked');
|
||||||
|
|
||||||
|
if (showAuthOptions) {
|
||||||
|
this.ui.authOptions.slideDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
this.ui.authOptions.slideUp();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_setSslOptionsVisibility: function () {
|
||||||
|
|
||||||
|
var showSslOptions = this.ui.sslToggle.prop('checked');
|
||||||
|
|
||||||
|
if (showSslOptions) {
|
||||||
|
this.ui.sslOptions.slideDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
this.ui.sslOptions.slideUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return AsModelBoundView.call(view);
|
return AsModelBoundView.call(view);
|
||||||
});
|
});
|
||||||
|
@ -39,6 +39,9 @@
|
|||||||
{{#if id}}
|
{{#if id}}
|
||||||
<button class="btn btn-danger pull-left x-remove">delete</button>
|
<button class="btn btn-danger pull-left x-remove">delete</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
<span class="x-activity"></span>
|
||||||
|
|
||||||
<button class="btn" data-dismiss="modal">cancel</button>
|
<button class="btn" data-dismiss="modal">cancel</button>
|
||||||
|
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
|
@ -11,6 +11,10 @@ define(
|
|||||||
var view = Marionette.ItemView.extend({
|
var view = Marionette.ItemView.extend({
|
||||||
template: 'Settings/Indexers/EditTemplate',
|
template: 'Settings/Indexers/EditTemplate',
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
activity: '.x-activity'
|
||||||
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
'click .x-save' : '_save',
|
'click .x-save' : '_save',
|
||||||
'click .x-save-and-add': '_saveAndAdd'
|
'click .x-save-and-add': '_saveAndAdd'
|
||||||
@ -21,6 +25,8 @@ define(
|
|||||||
},
|
},
|
||||||
|
|
||||||
_save: function () {
|
_save: function () {
|
||||||
|
this.ui.activity.html('<i class="icon-nd-spinner"></i>');
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var promise = this.model.saveSettings();
|
var promise = this.model.saveSettings();
|
||||||
|
|
||||||
@ -29,10 +35,16 @@ define(
|
|||||||
self.indexerCollection.add(self.model, { merge: true });
|
self.indexerCollection.add(self.model, { merge: true });
|
||||||
App.vent.trigger(App.Commands.CloseModalCommand);
|
App.vent.trigger(App.Commands.CloseModalCommand);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
promise.fail(function () {
|
||||||
|
self.ui.activity.empty();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_saveAndAdd: function () {
|
_saveAndAdd: function () {
|
||||||
|
this.ui.activity.html('<i class="icon-nd-spinner"></i>');
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var promise = this.model.saveSettings();
|
var promise = this.model.saveSettings();
|
||||||
|
|
||||||
@ -50,6 +62,10 @@ define(
|
|||||||
self.model.set('fields.' + key + '.value', '');
|
self.model.set('fields.' + key + '.value', '');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
promise.fail(function () {
|
||||||
|
self.ui.activity.empty();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -4,10 +4,10 @@ define(
|
|||||||
[
|
[
|
||||||
'marionette',
|
'marionette',
|
||||||
'Settings/Indexers/CollectionView',
|
'Settings/Indexers/CollectionView',
|
||||||
'Settings/Indexers/Options/View'
|
'Settings/Indexers/Options/IndexerOptionsView'
|
||||||
], function (Marionette, CollectionView, OptionsView) {
|
], function (Marionette, CollectionView, OptionsView) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'Settings/Indexers/LayoutTemplate',
|
template: 'Settings/Indexers/IndexerLayoutTemplate',
|
||||||
|
|
||||||
regions: {
|
regions: {
|
||||||
indexersRegion : '#indexers-collection',
|
indexersRegion : '#indexers-collection',
|
@ -6,7 +6,7 @@ define(
|
|||||||
], function (Marionette, AsModelBoundView) {
|
], function (Marionette, AsModelBoundView) {
|
||||||
|
|
||||||
var view = Marionette.ItemView.extend({
|
var view = Marionette.ItemView.extend({
|
||||||
template: 'Settings/MediaManagement/FileManagement/ViewTemplate'
|
template: 'Settings/Indexers/Options/IndexerOptionsViewTemplate'
|
||||||
});
|
});
|
||||||
|
|
||||||
return AsModelBoundView.call(view);
|
return AsModelBoundView.call(view);
|
@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group advanced-setting">
|
||||||
<label class="control-label">RSS Sync Interval</label>
|
<label class="control-label">RSS Sync Interval</label>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group advanced-setting">
|
||||||
<label class="control-label">Release Restrictions</label>
|
<label class="control-label">Release Restrictions</label>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
@ -1,13 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'marionette',
|
|
||||||
'Mixins/AsModelBoundView'
|
|
||||||
], function (Marionette, AsModelBoundView) {
|
|
||||||
|
|
||||||
var view = Marionette.ItemView.extend({
|
|
||||||
template: 'Settings/Indexers/Options/ViewTemplate'
|
|
||||||
});
|
|
||||||
|
|
||||||
return AsModelBoundView.call(view);
|
|
||||||
});
|
|
@ -0,0 +1,22 @@
|
|||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'marionette',
|
||||||
|
'Mixins/AsModelBoundView',
|
||||||
|
'Mixins/AutoComplete'
|
||||||
|
], function (Marionette, AsModelBoundView) {
|
||||||
|
|
||||||
|
var view = Marionette.ItemView.extend({
|
||||||
|
template: 'Settings/MediaManagement/FileManagement/FileManagementViewTemplate',
|
||||||
|
|
||||||
|
ui: {
|
||||||
|
recyclingBin: '.x-path'
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow: function () {
|
||||||
|
this.ui.recyclingBin.autoComplete('/directories');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return AsModelBoundView.call(view);
|
||||||
|
});
|
@ -1,4 +1,4 @@
|
|||||||
<fieldset>
|
<fieldset class="advanced-setting">
|
||||||
<legend>File Management</legend>
|
<legend>File Management</legend>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
@ -40,4 +40,15 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Recycling Bin</label>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" name="recycleBin" class="x-path"/>
|
||||||
|
<span class="help-inline">
|
||||||
|
<i class="icon-nd-form-info" title="Episode files will go here when deleted instead of being permanently deleted"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
@ -5,10 +5,10 @@ define(
|
|||||||
'marionette',
|
'marionette',
|
||||||
'Settings/MediaManagement/Naming/View',
|
'Settings/MediaManagement/Naming/View',
|
||||||
'Settings/MediaManagement/Sorting/View',
|
'Settings/MediaManagement/Sorting/View',
|
||||||
'Settings/MediaManagement/FileManagement/View'
|
'Settings/MediaManagement/FileManagement/FileManagementView'
|
||||||
], function (Marionette, NamingView, SortingView, FileManagementView) {
|
], function (Marionette, NamingView, SortingView, FileManagementView) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'Settings/MediaManagement/LayoutTemplate',
|
template: 'Settings/MediaManagement/MediaManagementLayoutTemplate',
|
||||||
|
|
||||||
regions: {
|
regions: {
|
||||||
episodeNaming : '#episode-naming',
|
episodeNaming : '#episode-naming',
|
@ -6,15 +6,16 @@ define(
|
|||||||
'Settings/SettingsModel',
|
'Settings/SettingsModel',
|
||||||
'Settings/General/GeneralSettingsModel',
|
'Settings/General/GeneralSettingsModel',
|
||||||
'Settings/MediaManagement/Naming/Model',
|
'Settings/MediaManagement/Naming/Model',
|
||||||
'Settings/MediaManagement/Layout',
|
'Settings/MediaManagement/MediaManagementLayout',
|
||||||
'Settings/Quality/QualityLayout',
|
'Settings/Quality/QualityLayout',
|
||||||
'Settings/Indexers/Layout',
|
'Settings/Indexers/IndexerLayout',
|
||||||
'Settings/Indexers/Collection',
|
'Settings/Indexers/Collection',
|
||||||
'Settings/DownloadClient/Layout',
|
'Settings/DownloadClient/Layout',
|
||||||
'Settings/Notifications/CollectionView',
|
'Settings/Notifications/CollectionView',
|
||||||
'Settings/Notifications/Collection',
|
'Settings/Notifications/Collection',
|
||||||
'Settings/General/GeneralView',
|
'Settings/General/GeneralView',
|
||||||
'Shared/LoadingView'
|
'Shared/LoadingView',
|
||||||
|
'Config'
|
||||||
], function (App,
|
], function (App,
|
||||||
Marionette,
|
Marionette,
|
||||||
SettingsModel,
|
SettingsModel,
|
||||||
@ -28,7 +29,8 @@ define(
|
|||||||
NotificationCollectionView,
|
NotificationCollectionView,
|
||||||
NotificationCollection,
|
NotificationCollection,
|
||||||
GeneralView,
|
GeneralView,
|
||||||
LoadingView) {
|
LoadingView,
|
||||||
|
Config) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'Settings/SettingsLayoutTemplate',
|
template: 'Settings/SettingsLayoutTemplate',
|
||||||
|
|
||||||
@ -48,7 +50,8 @@ define(
|
|||||||
indexersTab : '.x-indexers-tab',
|
indexersTab : '.x-indexers-tab',
|
||||||
downloadClientTab : '.x-download-client-tab',
|
downloadClientTab : '.x-download-client-tab',
|
||||||
notificationsTab : '.x-notifications-tab',
|
notificationsTab : '.x-notifications-tab',
|
||||||
generalTab : '.x-general-tab'
|
generalTab : '.x-general-tab',
|
||||||
|
advancedSettings : '.x-advanced-settings'
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
@ -58,7 +61,67 @@ define(
|
|||||||
'click .x-download-client-tab' : '_showDownloadClient',
|
'click .x-download-client-tab' : '_showDownloadClient',
|
||||||
'click .x-notifications-tab' : '_showNotifications',
|
'click .x-notifications-tab' : '_showNotifications',
|
||||||
'click .x-general-tab' : '_showGeneral',
|
'click .x-general-tab' : '_showGeneral',
|
||||||
'click .x-save-settings' : '_save'
|
'click .x-save-settings' : '_save',
|
||||||
|
'change .x-advanced-settings' : '_toggleAdvancedSettings'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function (options) {
|
||||||
|
if (options.action) {
|
||||||
|
this.action = options.action.toLowerCase();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onRender: function () {
|
||||||
|
this.loading.show(new LoadingView());
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.settings = new SettingsModel();
|
||||||
|
this.generalSettings = new GeneralSettingsModel();
|
||||||
|
this.namingSettings = new NamingModel();
|
||||||
|
this.indexerSettings = new IndexerCollection();
|
||||||
|
this.notificationSettings = new NotificationCollection();
|
||||||
|
|
||||||
|
$.when(this.settings.fetch(),
|
||||||
|
this.generalSettings.fetch(),
|
||||||
|
this.namingSettings.fetch(),
|
||||||
|
this.indexerSettings.fetch(),
|
||||||
|
this.notificationSettings.fetch()
|
||||||
|
).done(function () {
|
||||||
|
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.general.show(new GeneralView({ model: self.generalSettings }));
|
||||||
|
});
|
||||||
|
|
||||||
|
this._setAdvancedSettingsState();
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow: function () {
|
||||||
|
switch (this.action) {
|
||||||
|
case 'quality':
|
||||||
|
this._showQuality();
|
||||||
|
break;
|
||||||
|
case 'indexers':
|
||||||
|
this._showIndexers();
|
||||||
|
break;
|
||||||
|
case 'downloadclient':
|
||||||
|
this._showDownloadClient();
|
||||||
|
break;
|
||||||
|
case 'connect':
|
||||||
|
this._showNotifications();
|
||||||
|
break;
|
||||||
|
case 'notifications':
|
||||||
|
this._showNotifications();
|
||||||
|
break;
|
||||||
|
case 'general':
|
||||||
|
this._showGeneral();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._showMediaManagement();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_showMediaManagement: function (e) {
|
_showMediaManagement: function (e) {
|
||||||
@ -121,65 +184,30 @@ define(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function (options) {
|
|
||||||
if (options.action) {
|
|
||||||
this.action = options.action.toLowerCase();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onRender: function () {
|
|
||||||
this.loading.show(new LoadingView());
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.settings = new SettingsModel();
|
|
||||||
this.generalSettings = new GeneralSettingsModel();
|
|
||||||
this.namingSettings = new NamingModel();
|
|
||||||
this.indexerSettings = new IndexerCollection();
|
|
||||||
this.notificationSettings = new NotificationCollection();
|
|
||||||
|
|
||||||
$.when(this.settings.fetch(),
|
|
||||||
this.generalSettings.fetch(),
|
|
||||||
this.namingSettings.fetch(),
|
|
||||||
this.indexerSettings.fetch(),
|
|
||||||
this.notificationSettings.fetch()
|
|
||||||
).done(function () {
|
|
||||||
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.general.show(new GeneralView({model: self.generalSettings}));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onShow: function () {
|
|
||||||
switch (this.action) {
|
|
||||||
case 'quality':
|
|
||||||
this._showQuality();
|
|
||||||
break;
|
|
||||||
case 'indexers':
|
|
||||||
this._showIndexers();
|
|
||||||
break;
|
|
||||||
case 'downloadclient':
|
|
||||||
this._showDownloadClient();
|
|
||||||
break;
|
|
||||||
case 'connect':
|
|
||||||
this._showNotifications();
|
|
||||||
break;
|
|
||||||
case 'notifications':
|
|
||||||
this._showNotifications();
|
|
||||||
break;
|
|
||||||
case 'general':
|
|
||||||
this._showGeneral();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this._showMediaManagement();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_save: function () {
|
_save: function () {
|
||||||
App.vent.trigger(App.Commands.SaveSettings);
|
App.vent.trigger(App.Commands.SaveSettings);
|
||||||
|
},
|
||||||
|
|
||||||
|
_setAdvancedSettingsState: function () {
|
||||||
|
var checked = Config.getValueBoolean('advancedSettings');
|
||||||
|
this.ui.advancedSettings.prop('checked', checked);
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
this.$el.addClass('show-advanced-settings');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_toggleAdvancedSettings: function () {
|
||||||
|
var checked = this.ui.advancedSettings.prop('checked');
|
||||||
|
Config.setValue('advancedSettings', checked);
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
this.$el.addClass('show-advanced-settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
this.$el.removeClass('show-advanced-settings');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,19 @@
|
|||||||
<li><a href="#notifications" class="x-notifications-tab no-router">Connect</a></li>
|
<li><a href="#notifications" class="x-notifications-tab no-router">Connect</a></li>
|
||||||
<li><a href="#general" class="x-general-tab no-router">General</a></li>
|
<li><a href="#general" class="x-general-tab no-router">General</a></li>
|
||||||
<li class="pull-right"><button class="btn btn-primary x-save-settings">Save</button></li>
|
<li class="pull-right"><button class="btn btn-primary x-save-settings">Save</button></li>
|
||||||
|
<li class="pull-right advanced-settings-toggle">
|
||||||
|
<label class="checkbox toggle well">
|
||||||
|
<input type="checkbox" class="x-advanced-settings"/>
|
||||||
|
<p>
|
||||||
|
<span>Show</span>
|
||||||
|
<span>Hide</span>
|
||||||
|
</p>
|
||||||
|
<div class="btn btn-warning slide-button"/>
|
||||||
|
</label>
|
||||||
|
<span class="help-inline-checkbox">
|
||||||
|
<i class="icon-nd-form-info" title="Show advanced options"/>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
@import "../Content/Bootstrap/variables";
|
||||||
@import "../Shared/Styles/clickable.less";
|
@import "../Shared/Styles/clickable.less";
|
||||||
|
|
||||||
@import "Indexers/indexers";
|
@import "Indexers/indexers";
|
||||||
@import "Quality/quality";
|
@import "Quality/quality";
|
||||||
@import "Notifications/notifications";
|
@import "Notifications/notifications";
|
||||||
@ -43,4 +43,38 @@ li.save-and-add:hover {
|
|||||||
.naming-example {
|
.naming-example {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-settings-toggle {
|
||||||
|
margin-right: 40px;
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
width : 100px;
|
||||||
|
margin-left : 0px;
|
||||||
|
display : inline-block;
|
||||||
|
padding-top : 0px;
|
||||||
|
margin-bottom : 0px;
|
||||||
|
margin-top : -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-inline-checkbox {
|
||||||
|
display : inline-block;
|
||||||
|
margin-top : -23px;
|
||||||
|
margin-bottom : 0;
|
||||||
|
vertical-align : middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-setting {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
.control-label {
|
||||||
|
color: @warningText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-advanced-settings {
|
||||||
|
.advanced-setting {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
@ -23,7 +23,7 @@ define(
|
|||||||
var leftVal = left.get(columnName);
|
var leftVal = left.get(columnName);
|
||||||
var rightVal = right.get(columnName);
|
var rightVal = right.get(columnName);
|
||||||
|
|
||||||
return self._comparator(leftVal, rightVal)
|
return self._comparator(leftVal, rightVal);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -31,7 +31,7 @@ define(
|
|||||||
var leftVal = left.get(columnName);
|
var leftVal = left.get(columnName);
|
||||||
var rightVal = right.get(columnName);
|
var rightVal = right.get(columnName);
|
||||||
|
|
||||||
return self._comparator(rightVal, leftVal)
|
return self._comparator(rightVal, leftVal);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ define(
|
|||||||
|
|
||||||
_comparator: function (leftVal, rightVal) {
|
_comparator: function (leftVal, rightVal) {
|
||||||
if (!leftVal && !rightVal) {
|
if (!leftVal && !rightVal) {
|
||||||
return 0
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!leftVal) {
|
if (!leftVal) {
|
||||||
@ -47,7 +47,7 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!rightVal) {
|
if (!rightVal) {
|
||||||
return 1
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leftVal === rightVal) {
|
if (leftVal === rightVal) {
|
||||||
|
@ -33,9 +33,9 @@ define(
|
|||||||
route: 'logs'
|
route: 'logs'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title : 'Check for Update',
|
title : 'Updates',
|
||||||
icon : 'icon-nd-update',
|
icon : 'icon-upload-alt',
|
||||||
command: 'applicationUpdate'
|
route : 'update'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
11
UI/Update/UpdateCollection.js
Normal file
11
UI/Update/UpdateCollection.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'backbone',
|
||||||
|
'Update/UpdateModel'
|
||||||
|
], function (Backbone, UpdateModel) {
|
||||||
|
return Backbone.Collection.extend({
|
||||||
|
url : window.NzbDrone.ApiRoot + '/update',
|
||||||
|
model: UpdateModel
|
||||||
|
});
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user