Co-Authored-By: ta264 <ta264@users.noreply.github.com>pull/5116/head
parent
b83bb2cade
commit
1169741c54
@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Composition
|
||||||
|
{
|
||||||
|
public class AssemblyLoader
|
||||||
|
{
|
||||||
|
static AssemblyLoader()
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ContainerResolveEventHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Assembly> Load(IEnumerable<string> assemblyNames)
|
||||||
|
{
|
||||||
|
var toLoad = assemblyNames.ToList();
|
||||||
|
toLoad.Add("Sonarr.Common");
|
||||||
|
toLoad.Add(OsInfo.IsWindows ? "Sonarr.Windows" : "Sonarr.Mono");
|
||||||
|
|
||||||
|
var toRegisterResolver = new List<string> { "System.Data.SQLite" };
|
||||||
|
toRegisterResolver.AddRange(assemblyNames.Intersect(new[] { "Sonarr.Core" }));
|
||||||
|
RegisterNativeResolver(toRegisterResolver);
|
||||||
|
|
||||||
|
var startupPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
|
||||||
|
return toLoad.Select(x =>
|
||||||
|
AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{x}.dll")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Assembly ContainerResolveEventHandler(object sender, ResolveEventArgs args)
|
||||||
|
{
|
||||||
|
var resolver = new AssemblyDependencyResolver(args.RequestingAssembly.Location);
|
||||||
|
var assemblyPath = resolver.ResolveAssemblyToPath(new AssemblyName(args.Name));
|
||||||
|
|
||||||
|
if (assemblyPath == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RegisterNativeResolver(IEnumerable<string> assemblyNames)
|
||||||
|
{
|
||||||
|
foreach (var name in assemblyNames)
|
||||||
|
{
|
||||||
|
// This ensures we look for sqlite3 using libsqlite3.so.0 on Linux and not libsqlite3.so which
|
||||||
|
// is less likely to exist.
|
||||||
|
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(
|
||||||
|
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{name}.dll"));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
NativeLibrary.SetDllImportResolver(assembly, LoadNativeLib);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// This can only be set once per assembly
|
||||||
|
// Catch required for NzbDrone.Host tests
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IntPtr LoadNativeLib(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)
|
||||||
|
{
|
||||||
|
var mappedName = libraryName;
|
||||||
|
if (OsInfo.IsLinux)
|
||||||
|
{
|
||||||
|
if (libraryName == "sqlite3")
|
||||||
|
{
|
||||||
|
mappedName = "libsqlite3.so.0";
|
||||||
|
}
|
||||||
|
else if (libraryName == "mediainfo")
|
||||||
|
{
|
||||||
|
mappedName = "libmediainfo.so.0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,136 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Runtime.Loader;
|
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
using NzbDrone.Common.Messaging;
|
|
||||||
using TinyIoC;
|
|
||||||
|
|
||||||
namespace NzbDrone.Common.Composition
|
|
||||||
{
|
|
||||||
public abstract class ContainerBuilderBase
|
|
||||||
{
|
|
||||||
private readonly List<Type> _loadedTypes;
|
|
||||||
|
|
||||||
protected IContainer Container { get; }
|
|
||||||
|
|
||||||
protected ContainerBuilderBase(IStartupContext args, List<string> assemblies)
|
|
||||||
{
|
|
||||||
_loadedTypes = new List<Type>();
|
|
||||||
|
|
||||||
assemblies.Add(OsInfo.IsWindows ? "Sonarr.Windows" : "Sonarr.Mono");
|
|
||||||
assemblies.Add("Sonarr.Common");
|
|
||||||
|
|
||||||
var startupPath = AppDomain.CurrentDomain.BaseDirectory;
|
|
||||||
|
|
||||||
foreach (var assemblyName in assemblies)
|
|
||||||
{
|
|
||||||
_loadedTypes.AddRange(AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{assemblyName}.dll")).GetExportedTypes());
|
|
||||||
}
|
|
||||||
|
|
||||||
var toRegisterResolver = new List<string> { "System.Data.SQLite" };
|
|
||||||
toRegisterResolver.AddRange(assemblies.Intersect(new[] { "Sonarr.Core" }));
|
|
||||||
RegisterNativeResolver(toRegisterResolver);
|
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ContainerResolveEventHandler);
|
|
||||||
|
|
||||||
Container = new Container(new TinyIoCContainer(), _loadedTypes);
|
|
||||||
AutoRegisterInterfaces();
|
|
||||||
Container.Register(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Assembly ContainerResolveEventHandler(object sender, ResolveEventArgs args)
|
|
||||||
{
|
|
||||||
var resolver = new AssemblyDependencyResolver(args.RequestingAssembly.Location);
|
|
||||||
var assemblyPath = resolver.ResolveAssemblyToPath(new AssemblyName(args.Name));
|
|
||||||
|
|
||||||
if (assemblyPath == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void RegisterNativeResolver(IEnumerable<string> assemblyNames)
|
|
||||||
{
|
|
||||||
// This ensures we look for sqlite3 using libsqlite3.so.0 on Linux and not libsqlite3.so which
|
|
||||||
// is less likely to exist.
|
|
||||||
foreach (var name in assemblyNames)
|
|
||||||
{
|
|
||||||
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(
|
|
||||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{name}.dll"));
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
NativeLibrary.SetDllImportResolver(assembly, LoadNativeLib);
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
// This can only be set once per assembly
|
|
||||||
// Catch required for NzbDrone.Host tests
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IntPtr LoadNativeLib(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)
|
|
||||||
{
|
|
||||||
var mappedName = libraryName;
|
|
||||||
if (OsInfo.IsLinux)
|
|
||||||
{
|
|
||||||
if (libraryName == "sqlite3")
|
|
||||||
{
|
|
||||||
mappedName = "libsqlite3.so.0";
|
|
||||||
}
|
|
||||||
else if (libraryName == "mediainfo")
|
|
||||||
{
|
|
||||||
mappedName = "libmediainfo.so.0";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AutoRegisterInterfaces()
|
|
||||||
{
|
|
||||||
var loadedInterfaces = _loadedTypes.Where(t => t.IsInterface).ToList();
|
|
||||||
var implementedInterfaces = _loadedTypes.SelectMany(t => t.GetInterfaces());
|
|
||||||
|
|
||||||
var contracts = loadedInterfaces.Union(implementedInterfaces).Where(c => !c.IsGenericTypeDefinition && !string.IsNullOrWhiteSpace(c.FullName))
|
|
||||||
.Where(c => !c.FullName.StartsWith("System"))
|
|
||||||
.Except(new List<Type> { typeof(IMessage), typeof(IEvent), typeof(IContainer) }).Distinct().OrderBy(c => c.FullName);
|
|
||||||
|
|
||||||
foreach (var contract in contracts)
|
|
||||||
{
|
|
||||||
AutoRegisterImplementations(contract);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void AutoRegisterImplementations<TContract>()
|
|
||||||
{
|
|
||||||
AutoRegisterImplementations(typeof(TContract));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AutoRegisterImplementations(Type contractType)
|
|
||||||
{
|
|
||||||
var implementations = Container.GetImplementations(contractType).Where(c => !c.IsGenericTypeDefinition).ToList();
|
|
||||||
|
|
||||||
if (implementations.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (implementations.Count == 1)
|
|
||||||
{
|
|
||||||
var impl = implementations.Single();
|
|
||||||
Container.RegisterSingleton(contractType, impl);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Container.RegisterAllAsSingleton(contractType, implementations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,41 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using DryIoc;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Composition.Extensions
|
||||||
|
{
|
||||||
|
public static class ServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static Rules WithNzbDroneRules(this Rules rules)
|
||||||
|
{
|
||||||
|
return rules.WithMicrosoftDependencyInjectionRules()
|
||||||
|
.WithAutoConcreteTypeResolution()
|
||||||
|
.WithDefaultReuse(Reuse.Singleton);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IContainer AddStartupContext(this IContainer container, StartupContext context)
|
||||||
|
{
|
||||||
|
container.RegisterInstance<IStartupContext>(context, ifAlreadyRegistered: IfAlreadyRegistered.Replace);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IContainer AutoAddServices(this IContainer container, List<string> assemblyNames)
|
||||||
|
{
|
||||||
|
var assemblies = AssemblyLoader.Load(assemblyNames);
|
||||||
|
|
||||||
|
container.RegisterMany(assemblies,
|
||||||
|
serviceTypeCondition: type => type.IsInterface && !string.IsNullOrWhiteSpace(type.FullName) && !type.FullName.StartsWith("System"),
|
||||||
|
reuse: Reuse.Singleton);
|
||||||
|
|
||||||
|
container.RegisterMany(assemblies,
|
||||||
|
serviceTypeCondition: type => !type.IsInterface && !string.IsNullOrWhiteSpace(type.FullName) && !type.FullName.StartsWith("System"),
|
||||||
|
reuse: Reuse.Transient);
|
||||||
|
|
||||||
|
var knownTypes = new KnownTypes(assemblies.SelectMany(x => x.GetTypes()).ToList());
|
||||||
|
container.RegisterInstance(knownTypes);
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Composition
|
||||||
|
{
|
||||||
|
public class KnownTypes
|
||||||
|
{
|
||||||
|
private List<Type> _knownTypes;
|
||||||
|
|
||||||
|
// So unity can resolve for tests
|
||||||
|
public KnownTypes()
|
||||||
|
: this(new List<Type>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public KnownTypes(List<Type> loadedTypes)
|
||||||
|
{
|
||||||
|
_knownTypes = loadedTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Type> GetImplementations(Type contractType)
|
||||||
|
{
|
||||||
|
return _knownTypes
|
||||||
|
.Where(implementation =>
|
||||||
|
contractType.IsAssignableFrom(implementation) &&
|
||||||
|
!implementation.IsInterface &&
|
||||||
|
!implementation.IsAbstract);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using DryIoc;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Instrumentation.Extensions
|
||||||
|
{
|
||||||
|
public static class CompositionExtensions
|
||||||
|
{
|
||||||
|
public static IContainer AddNzbDroneLogger(this IContainer container)
|
||||||
|
{
|
||||||
|
container.Register(Made.Of<Logger>(() => LogManager.GetLogger(Arg.Index<string>(0)), r => r.Parent.ImplementationType.Name.ToString()), reuse: Reuse.Transient);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
|||||||
|
using DryIoc;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Extensions
|
||||||
|
{
|
||||||
|
public static class CompositionExtensions
|
||||||
|
{
|
||||||
|
public static IContainer AddDatabase(this IContainer container)
|
||||||
|
{
|
||||||
|
container.RegisterDelegate<IDbFactory, IMainDatabase>(f => new MainDatabase(f.Create()), Reuse.Singleton);
|
||||||
|
container.RegisterDelegate<IDbFactory, ILogDatabase>(f => new LogDatabase(f.Create(MigrationType.Log)), Reuse.Singleton);
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IContainer AddDummyDatabase(this IContainer container)
|
||||||
|
{
|
||||||
|
container.RegisterInstance<IMainDatabase>(new MainDatabase(null));
|
||||||
|
container.RegisterInstance<ILogDatabase>(new LogDatabase(null));
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
using FluentMigrator.Runner;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using NLog;
|
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore.Migration.Framework
|
|
||||||
{
|
|
||||||
public class MigrationLoggerProvider : ILoggerProvider
|
|
||||||
{
|
|
||||||
private readonly Logger _logger;
|
|
||||||
|
|
||||||
public MigrationLoggerProvider(Logger logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ILogger CreateLogger(string categoryName)
|
|
||||||
{
|
|
||||||
return new MigrationLogger(_logger, new FluentMigratorLoggerOptions() { ShowElapsedTime = true, ShowSql = true });
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,44 @@
|
|||||||
|
using FluentValidation.Validators;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Validation.Paths
|
||||||
|
{
|
||||||
|
public class RecycleBinValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
|
||||||
|
public RecycleBinValidator(IConfigService configService)
|
||||||
|
: base("Path is {relationship} configured recycle bin folder")
|
||||||
|
{
|
||||||
|
_configService = configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
var recycleBin = _configService.RecycleBin;
|
||||||
|
var folder = context.PropertyValue.ToString();
|
||||||
|
|
||||||
|
if (context.PropertyValue == null || recycleBin.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recycleBin.PathEquals(folder))
|
||||||
|
{
|
||||||
|
context.MessageFormatter.AppendArgument("relationship", "set to");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recycleBin.IsParentPath(folder))
|
||||||
|
{
|
||||||
|
context.MessageFormatter.AppendArgument("relationship", "child of");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using FluentValidation.Validators;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.RootFolders;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Validation.Paths
|
||||||
|
{
|
||||||
|
public class RootFolderAncestorValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
private readonly IRootFolderService _rootFolderService;
|
||||||
|
|
||||||
|
public RootFolderAncestorValidator(IRootFolderService rootFolderService)
|
||||||
|
: base("Path is an ancestor of an existing root folder")
|
||||||
|
{
|
||||||
|
_rootFolderService = rootFolderService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
if (context.PropertyValue == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !_rootFolderService.All().Any(s => context.PropertyValue.ToString().IsParentPath(s.Path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Common.Processes;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Lifecycle;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Host;
|
||||||
|
|
||||||
|
namespace NzbDrone.Host
|
||||||
|
{
|
||||||
|
public class AppLifetime : IHostedService, IHandle<ApplicationShutdownRequested>
|
||||||
|
{
|
||||||
|
private readonly IHostApplicationLifetime _appLifetime;
|
||||||
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
private readonly IRuntimeInfo _runtimeInfo;
|
||||||
|
private readonly IStartupContext _startupContext;
|
||||||
|
private readonly IBrowserService _browserService;
|
||||||
|
private readonly IProcessProvider _processProvider;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly IUtilityModeRouter _utilityModeRouter;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public AppLifetime(IHostApplicationLifetime appLifetime,
|
||||||
|
IConfigFileProvider configFileProvider,
|
||||||
|
IRuntimeInfo runtimeInfo,
|
||||||
|
IStartupContext startupContext,
|
||||||
|
IBrowserService browserService,
|
||||||
|
IProcessProvider processProvider,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
|
IUtilityModeRouter utilityModeRouter,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_appLifetime = appLifetime;
|
||||||
|
_configFileProvider = configFileProvider;
|
||||||
|
_runtimeInfo = runtimeInfo;
|
||||||
|
_startupContext = startupContext;
|
||||||
|
_browserService = browserService;
|
||||||
|
_processProvider = processProvider;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_utilityModeRouter = utilityModeRouter;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
appLifetime.ApplicationStarted.Register(OnAppStarted);
|
||||||
|
appLifetime.ApplicationStopped.Register(OnAppStopped);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAppStarted()
|
||||||
|
{
|
||||||
|
_runtimeInfo.IsExiting = false;
|
||||||
|
|
||||||
|
if (!_startupContext.Flags.Contains(StartupContext.NO_BROWSER)
|
||||||
|
&& _configFileProvider.LaunchBrowser)
|
||||||
|
{
|
||||||
|
_browserService.LaunchWebUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(new ApplicationStartedEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAppStopped()
|
||||||
|
{
|
||||||
|
if (_runtimeInfo.RestartPending)
|
||||||
|
{
|
||||||
|
var restartArgs = GetRestartArgs();
|
||||||
|
|
||||||
|
_logger.Info("Attempting restart with arguments: {0}", restartArgs);
|
||||||
|
_processProvider.SpawnNewProcess(_runtimeInfo.ExecutingApplication, restartArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Shutdown()
|
||||||
|
{
|
||||||
|
_logger.Info("Attempting to stop application.");
|
||||||
|
_logger.Info("Application has finished stop routine.");
|
||||||
|
_runtimeInfo.IsExiting = true;
|
||||||
|
_appLifetime.StopApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetRestartArgs()
|
||||||
|
{
|
||||||
|
var args = _startupContext.PreservedArguments;
|
||||||
|
|
||||||
|
args += " /restart";
|
||||||
|
|
||||||
|
if (!args.Contains("/nobrowser"))
|
||||||
|
{
|
||||||
|
args += " /nobrowser";
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ApplicationShutdownRequested message)
|
||||||
|
{
|
||||||
|
if (!_runtimeInfo.IsWindowsService)
|
||||||
|
{
|
||||||
|
if (message.Restarting)
|
||||||
|
{
|
||||||
|
_runtimeInfo.RestartPending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogManager.Configuration = null;
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,136 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ServiceProcess;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Composition;
|
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Datastore;
|
|
||||||
using NzbDrone.Core.Lifecycle;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
|
||||||
|
|
||||||
namespace NzbDrone.Host
|
|
||||||
{
|
|
||||||
public interface INzbDroneServiceFactory
|
|
||||||
{
|
|
||||||
ServiceBase Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface INzbDroneConsoleFactory
|
|
||||||
{
|
|
||||||
void Start();
|
|
||||||
void Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory
|
|
||||||
{
|
|
||||||
private readonly INzbDroneConsoleFactory _consoleFactory;
|
|
||||||
|
|
||||||
public NzbDroneServiceFactory(INzbDroneConsoleFactory consoleFactory)
|
|
||||||
{
|
|
||||||
_consoleFactory = consoleFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnStart(string[] args)
|
|
||||||
{
|
|
||||||
_consoleFactory.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnStop()
|
|
||||||
{
|
|
||||||
_consoleFactory.Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceBase Build()
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DummyNzbDroneServiceFactory : INzbDroneServiceFactory
|
|
||||||
{
|
|
||||||
public ServiceBase Build()
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NzbDroneConsoleFactory : INzbDroneConsoleFactory, IHandle<ApplicationShutdownRequested>
|
|
||||||
{
|
|
||||||
private readonly IConfigFileProvider _configFileProvider;
|
|
||||||
private readonly IRuntimeInfo _runtimeInfo;
|
|
||||||
private readonly IHostController _hostController;
|
|
||||||
private readonly IStartupContext _startupContext;
|
|
||||||
private readonly IBrowserService _browserService;
|
|
||||||
private readonly IContainer _container;
|
|
||||||
private readonly Logger _logger;
|
|
||||||
|
|
||||||
// private CancelHandler _cancelHandler;
|
|
||||||
public NzbDroneConsoleFactory(IConfigFileProvider configFileProvider,
|
|
||||||
IHostController hostController,
|
|
||||||
IRuntimeInfo runtimeInfo,
|
|
||||||
IStartupContext startupContext,
|
|
||||||
IBrowserService browserService,
|
|
||||||
IContainer container,
|
|
||||||
Logger logger)
|
|
||||||
{
|
|
||||||
_configFileProvider = configFileProvider;
|
|
||||||
_hostController = hostController;
|
|
||||||
_runtimeInfo = runtimeInfo;
|
|
||||||
_startupContext = startupContext;
|
|
||||||
_browserService = browserService;
|
|
||||||
_container = container;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
if (OsInfo.IsNotWindows)
|
|
||||||
{
|
|
||||||
//Console.CancelKeyPress += (sender, eventArgs) => eventArgs.Cancel = true;
|
|
||||||
//_cancelHandler = new CancelHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
_runtimeInfo.IsExiting = false;
|
|
||||||
DbFactory.RegisterDatabase(_container);
|
|
||||||
|
|
||||||
_container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartingEvent());
|
|
||||||
|
|
||||||
if (_runtimeInfo.IsExiting)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_hostController.StartServer();
|
|
||||||
|
|
||||||
if (!_startupContext.Flags.Contains(StartupContext.NO_BROWSER)
|
|
||||||
&& _configFileProvider.LaunchBrowser)
|
|
||||||
{
|
|
||||||
_browserService.LaunchWebUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
_container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartedEvent());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Shutdown()
|
|
||||||
{
|
|
||||||
_logger.Info("Attempting to stop application.");
|
|
||||||
_hostController.StopServer();
|
|
||||||
_logger.Info("Application has finished stop routine.");
|
|
||||||
_runtimeInfo.IsExiting = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Handle(ApplicationShutdownRequested message)
|
|
||||||
{
|
|
||||||
if (!_runtimeInfo.IsWindowsService)
|
|
||||||
{
|
|
||||||
if (message.Restarting)
|
|
||||||
{
|
|
||||||
_runtimeInfo.RestartPending = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
LogManager.Configuration = null;
|
|
||||||
Shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
namespace NzbDrone.Host
|
|
||||||
{
|
|
||||||
public interface IHostController
|
|
||||||
{
|
|
||||||
void StartServer();
|
|
||||||
void StopServer();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
namespace NzbDrone.Host.AccessControl
|
|
||||||
{
|
|
||||||
public interface IRemoteAccessAdapter
|
|
||||||
{
|
|
||||||
void MakeAccessible(bool passive);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Nancy.Bootstrapper;
|
|
||||||
using NzbDrone.Common.Composition;
|
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
using NzbDrone.SignalR;
|
|
||||||
using Sonarr.Http;
|
|
||||||
|
|
||||||
namespace NzbDrone.Host
|
|
||||||
{
|
|
||||||
public class MainAppContainerBuilder : ContainerBuilderBase
|
|
||||||
{
|
|
||||||
public static IContainer BuildContainer(StartupContext args)
|
|
||||||
{
|
|
||||||
var assemblies = new List<string>
|
|
||||||
{
|
|
||||||
"Sonarr.Host",
|
|
||||||
"Sonarr.Core",
|
|
||||||
"Sonarr.SignalR",
|
|
||||||
"Sonarr.Api.V3",
|
|
||||||
"Sonarr.Http"
|
|
||||||
};
|
|
||||||
|
|
||||||
return new MainAppContainerBuilder(args, assemblies).Container;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MainAppContainerBuilder(StartupContext args, List<string> assemblies)
|
|
||||||
: base(args, assemblies)
|
|
||||||
{
|
|
||||||
AutoRegisterImplementations<MessageHub>();
|
|
||||||
|
|
||||||
Container.Register<INancyBootstrapper, SonarrBootstrapper>();
|
|
||||||
|
|
||||||
if (OsInfo.IsWindows)
|
|
||||||
{
|
|
||||||
Container.Register<INzbDroneServiceFactory, NzbDroneServiceFactory>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Container.Register<INzbDroneServiceFactory, DummyNzbDroneServiceFactory>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Reflection;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
using NzbDrone.Common.Instrumentation;
|
|
||||||
|
|
||||||
namespace NzbDrone.Host
|
|
||||||
{
|
|
||||||
public static class PlatformValidation
|
|
||||||
{
|
|
||||||
private const string DOWNLOAD_LINK = "http://www.microsoft.com/en-us/download/details.aspx?id=42643";
|
|
||||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(PlatformValidation));
|
|
||||||
|
|
||||||
public static bool IsValidate(IUserAlert userAlert)
|
|
||||||
{
|
|
||||||
if (OsInfo.IsNotWindows)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsAssemblyAvailable("System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"))
|
|
||||||
{
|
|
||||||
userAlert.Alert("It looks like you don't have the correct version of .NET Framework installed. You will now be directed the download page.");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Process.Start(DOWNLOAD_LINK);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
userAlert.Alert("Oops. Couldn't start your browser. Please visit http://www.microsoft.com/net to download the latest version of .NET Framework");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsAssemblyAvailable(string assemblyString)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Assembly.Load(assemblyString);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Warn(e, "Couldn't load {0}", assemblyString);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,217 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NLog.Extensions.Logging;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Common.Instrumentation;
|
||||||
|
using NzbDrone.Common.Processes;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Instrumentation;
|
||||||
|
using NzbDrone.Host;
|
||||||
|
using NzbDrone.Host.AccessControl;
|
||||||
|
using NzbDrone.Http.Authentication;
|
||||||
|
using NzbDrone.SignalR;
|
||||||
|
using Sonarr.Api.V3.System;
|
||||||
|
using Sonarr.Http;
|
||||||
|
using Sonarr.Http.Authentication;
|
||||||
|
using Sonarr.Http.ErrorManagement;
|
||||||
|
using Sonarr.Http.Frontend;
|
||||||
|
using Sonarr.Http.Middleware;
|
||||||
|
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||||
|
|
||||||
|
namespace NzbDrone.Host
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddLogging(b =>
|
||||||
|
{
|
||||||
|
b.ClearProviders();
|
||||||
|
b.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
|
||||||
|
b.AddFilter("Microsoft.AspNetCore", Microsoft.Extensions.Logging.LogLevel.Warning);
|
||||||
|
b.AddFilter("Sonarr.Http.Authentication", LogLevel.Information);
|
||||||
|
b.AddFilter("Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager", LogLevel.Error);
|
||||||
|
b.AddNLog();
|
||||||
|
});
|
||||||
|
|
||||||
|
services.Configure<ForwardedHeadersOptions>(options =>
|
||||||
|
{
|
||||||
|
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
||||||
|
options.KnownNetworks.Clear();
|
||||||
|
options.KnownProxies.Clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddRouting(options => options.LowercaseUrls = true);
|
||||||
|
|
||||||
|
services.AddResponseCompression();
|
||||||
|
|
||||||
|
services.AddCors(options =>
|
||||||
|
{
|
||||||
|
options.AddPolicy(VersionedApiControllerAttribute.API_CORS_POLICY,
|
||||||
|
builder =>
|
||||||
|
builder.AllowAnyOrigin()
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowAnyHeader());
|
||||||
|
|
||||||
|
options.AddPolicy("AllowGet",
|
||||||
|
builder =>
|
||||||
|
builder.AllowAnyOrigin()
|
||||||
|
.WithMethods("GET", "OPTIONS")
|
||||||
|
.AllowAnyHeader());
|
||||||
|
});
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddControllers(options =>
|
||||||
|
{
|
||||||
|
options.ReturnHttpNotAcceptable = true;
|
||||||
|
})
|
||||||
|
.AddApplicationPart(typeof(SystemController).Assembly)
|
||||||
|
.AddApplicationPart(typeof(StaticResourceController).Assembly)
|
||||||
|
.AddJsonOptions(options =>
|
||||||
|
{
|
||||||
|
STJson.ApplySerializerSettings(options.JsonSerializerOptions);
|
||||||
|
})
|
||||||
|
.AddControllersAsServices();
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddSignalR()
|
||||||
|
.AddJsonProtocol(options =>
|
||||||
|
{
|
||||||
|
options.PayloadSerializerOptions = STJson.GetSerializerSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddDataProtection()
|
||||||
|
.PersistKeysToFileSystem(new DirectoryInfo(Configuration["dataProtectionFolder"]));
|
||||||
|
|
||||||
|
services.AddSingleton<IAuthorizationPolicyProvider, UiAuthorizationPolicyProvider>();
|
||||||
|
services.AddAuthorization(options =>
|
||||||
|
{
|
||||||
|
options.AddPolicy("SignalR", policy =>
|
||||||
|
{
|
||||||
|
policy.AuthenticationSchemes.Add("SignalR");
|
||||||
|
policy.RequireAuthenticatedUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Require auth on everything except those marked [AllowAnonymous]
|
||||||
|
options.FallbackPolicy = new AuthorizationPolicyBuilder("API")
|
||||||
|
.RequireAuthenticatedUser()
|
||||||
|
.Build();
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddAppAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app,
|
||||||
|
IStartupContext startupContext,
|
||||||
|
Lazy<IMainDatabase> mainDatabaseFactory,
|
||||||
|
Lazy<ILogDatabase> logDatabaseFactory,
|
||||||
|
DatabaseTarget dbTarget,
|
||||||
|
ISingleInstancePolicy singleInstancePolicy,
|
||||||
|
InitializeLogger initializeLogger,
|
||||||
|
ReconfigureLogging reconfigureLogging,
|
||||||
|
IAppFolderFactory appFolderFactory,
|
||||||
|
IProvidePidFile pidFileProvider,
|
||||||
|
IConfigFileProvider configFileProvider,
|
||||||
|
IRuntimeInfo runtimeInfo,
|
||||||
|
IFirewallAdapter firewallAdapter,
|
||||||
|
SonarrErrorPipeline errorHandler)
|
||||||
|
{
|
||||||
|
initializeLogger.Initialize();
|
||||||
|
appFolderFactory.Register();
|
||||||
|
pidFileProvider.Write();
|
||||||
|
|
||||||
|
reconfigureLogging.Reconfigure();
|
||||||
|
|
||||||
|
EnsureSingleInstance(false, startupContext, singleInstancePolicy);
|
||||||
|
|
||||||
|
// instantiate the databases to initialize/migrate them
|
||||||
|
_ = mainDatabaseFactory.Value;
|
||||||
|
_ = logDatabaseFactory.Value;
|
||||||
|
|
||||||
|
dbTarget.Register();
|
||||||
|
|
||||||
|
if (OsInfo.IsNotWindows)
|
||||||
|
{
|
||||||
|
Console.CancelKeyPress += (sender, eventArgs) => NLog.LogManager.Configuration = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OsInfo.IsWindows && runtimeInfo.IsAdmin)
|
||||||
|
{
|
||||||
|
firewallAdapter.MakeAccessible();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseForwardedHeaders();
|
||||||
|
app.UseMiddleware<LoggingMiddleware>();
|
||||||
|
app.UsePathBase(new PathString(configFileProvider.UrlBase));
|
||||||
|
app.UseExceptionHandler(new ExceptionHandlerOptions
|
||||||
|
{
|
||||||
|
AllowStatusCode404Response = true,
|
||||||
|
ExceptionHandler = errorHandler.HandleException
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
app.UseCors();
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
app.UseResponseCompression();
|
||||||
|
app.Properties["host.AppName"] = BuildInfo.AppName;
|
||||||
|
|
||||||
|
app.UseMiddleware<VersionMiddleware>();
|
||||||
|
app.UseMiddleware<UrlBaseMiddleware>(configFileProvider.UrlBase);
|
||||||
|
app.UseMiddleware<CacheHeaderMiddleware>();
|
||||||
|
app.UseMiddleware<IfModifiedMiddleware>();
|
||||||
|
app.UseMiddleware<BufferingMiddleware>(new List<string> { "/api/v3/command" });
|
||||||
|
|
||||||
|
app.UseWebSockets();
|
||||||
|
|
||||||
|
app.UseEndpoints(x =>
|
||||||
|
{
|
||||||
|
x.MapHub<MessageHub>("/signalr/messages").RequireAuthorization("SignalR");
|
||||||
|
x.MapControllers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureSingleInstance(bool isService, IStartupContext startupContext, ISingleInstancePolicy instancePolicy)
|
||||||
|
{
|
||||||
|
if (startupContext.Flags.Contains(StartupContext.NO_SINGLE_INSTANCE_CHECK))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startupContext.Flags.Contains(StartupContext.TERMINATE))
|
||||||
|
{
|
||||||
|
instancePolicy.KillAllOtherInstance();
|
||||||
|
}
|
||||||
|
else if (startupContext.Args.ContainsKey(StartupContext.APPDATA))
|
||||||
|
{
|
||||||
|
instancePolicy.WarnIfAlreadyRunning();
|
||||||
|
}
|
||||||
|
else if (isService)
|
||||||
|
{
|
||||||
|
instancePolicy.KillAllOtherInstance();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
instancePolicy.PreventStartIfAlreadyRunning();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
|
|
||||||
namespace NzbDrone.Host.Middleware
|
|
||||||
{
|
|
||||||
public interface IAspNetCoreMiddleware
|
|
||||||
{
|
|
||||||
int Order { get; }
|
|
||||||
void Attach(IApplicationBuilder appBuilder);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Nancy.Bootstrapper;
|
|
||||||
using Nancy.Owin;
|
|
||||||
|
|
||||||
namespace NzbDrone.Host.Middleware
|
|
||||||
{
|
|
||||||
public class NancyMiddleware : IAspNetCoreMiddleware
|
|
||||||
{
|
|
||||||
private readonly INancyBootstrapper _nancyBootstrapper;
|
|
||||||
|
|
||||||
public int Order => 2;
|
|
||||||
|
|
||||||
public NancyMiddleware(INancyBootstrapper nancyBootstrapper)
|
|
||||||
{
|
|
||||||
_nancyBootstrapper = nancyBootstrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Attach(IApplicationBuilder appBuilder)
|
|
||||||
{
|
|
||||||
var options = new NancyOptions
|
|
||||||
{
|
|
||||||
Bootstrapper = _nancyBootstrapper,
|
|
||||||
PerformPassThrough = context => context.Request.Path.StartsWith("/signalr")
|
|
||||||
};
|
|
||||||
|
|
||||||
appBuilder.UseOwin(x => x.UseNancy(options));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Composition;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.SignalR;
|
|
||||||
|
|
||||||
namespace NzbDrone.Host.Middleware
|
|
||||||
{
|
|
||||||
public class SignalRMiddleware : IAspNetCoreMiddleware
|
|
||||||
{
|
|
||||||
private readonly IContainer _container;
|
|
||||||
private readonly Logger _logger;
|
|
||||||
private static string API_KEY;
|
|
||||||
private static string URL_BASE;
|
|
||||||
public int Order => 1;
|
|
||||||
|
|
||||||
public SignalRMiddleware(IContainer container,
|
|
||||||
IConfigFileProvider configFileProvider,
|
|
||||||
Logger logger)
|
|
||||||
{
|
|
||||||
_container = container;
|
|
||||||
_logger = logger;
|
|
||||||
API_KEY = configFileProvider.ApiKey;
|
|
||||||
URL_BASE = configFileProvider.UrlBase;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Attach(IApplicationBuilder appBuilder)
|
|
||||||
{
|
|
||||||
appBuilder.UseWebSockets();
|
|
||||||
|
|
||||||
appBuilder.Use(async (context, next) =>
|
|
||||||
{
|
|
||||||
if (context.Request.Path.StartsWithSegments("/signalr") &&
|
|
||||||
!context.Request.Path.Value.EndsWith("/negotiate"))
|
|
||||||
{
|
|
||||||
if (!context.Request.Query.ContainsKey("access_token") ||
|
|
||||||
context.Request.Query["access_token"] != API_KEY)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = 401;
|
|
||||||
await context.Response.WriteAsync("Unauthorized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await next();
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException e)
|
|
||||||
{
|
|
||||||
// Demote the exception to trace logging so users don't worry (as much).
|
|
||||||
_logger.Trace(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
appBuilder.UseEndpoints(x =>
|
|
||||||
{
|
|
||||||
x.MapHub<MessageHub>(URL_BASE + "/signalr/messages");
|
|
||||||
});
|
|
||||||
|
|
||||||
// This is a side effect of haing multiple IoC containers, TinyIoC and whatever
|
|
||||||
// Kestrel/SignalR is using. Ideally we'd have one IoC container, but that's non-trivial with TinyIoC
|
|
||||||
// TODO: Use a single IoC container if supported for TinyIoC or if we switch to another system (ie Autofac).
|
|
||||||
|
|
||||||
var hubContext = appBuilder.ApplicationServices.GetService<IHubContext<MessageHub>>();
|
|
||||||
_container.Register(hubContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,157 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using NLog;
|
|
||||||
using NLog.Extensions.Logging;
|
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
using NzbDrone.Common.Exceptions;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Serializer;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Host.AccessControl;
|
|
||||||
using NzbDrone.Host.Middleware;
|
|
||||||
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
|
||||||
|
|
||||||
namespace NzbDrone.Host
|
|
||||||
{
|
|
||||||
public class WebHostController : IHostController
|
|
||||||
{
|
|
||||||
private readonly IRuntimeInfo _runtimeInfo;
|
|
||||||
private readonly IConfigFileProvider _configFileProvider;
|
|
||||||
private readonly IFirewallAdapter _firewallAdapter;
|
|
||||||
private readonly IEnumerable<IAspNetCoreMiddleware> _middlewares;
|
|
||||||
private readonly Logger _logger;
|
|
||||||
private IWebHost _host;
|
|
||||||
|
|
||||||
public WebHostController(IRuntimeInfo runtimeInfo,
|
|
||||||
IConfigFileProvider configFileProvider,
|
|
||||||
IFirewallAdapter firewallAdapter,
|
|
||||||
IEnumerable<IAspNetCoreMiddleware> middlewares,
|
|
||||||
Logger logger)
|
|
||||||
{
|
|
||||||
_runtimeInfo = runtimeInfo;
|
|
||||||
_configFileProvider = configFileProvider;
|
|
||||||
_firewallAdapter = firewallAdapter;
|
|
||||||
_middlewares = middlewares;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartServer()
|
|
||||||
{
|
|
||||||
if (OsInfo.IsWindows)
|
|
||||||
{
|
|
||||||
if (_runtimeInfo.IsAdmin)
|
|
||||||
{
|
|
||||||
_firewallAdapter.MakeAccessible();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bindAddress = _configFileProvider.BindAddress;
|
|
||||||
var enableSsl = _configFileProvider.EnableSsl;
|
|
||||||
var sslCertPath = _configFileProvider.SslCertPath;
|
|
||||||
|
|
||||||
var urls = new List<string>();
|
|
||||||
|
|
||||||
urls.Add(BuildUrl("http", bindAddress, _configFileProvider.Port));
|
|
||||||
|
|
||||||
if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
urls.Add(BuildUrl("https", bindAddress, _configFileProvider.SslPort));
|
|
||||||
}
|
|
||||||
|
|
||||||
_host = new WebHostBuilder()
|
|
||||||
.UseUrls(urls.ToArray())
|
|
||||||
.UseKestrel(options =>
|
|
||||||
{
|
|
||||||
if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
options.ConfigureHttpsDefaults(configureOptions =>
|
|
||||||
{
|
|
||||||
X509Certificate2 certificate;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
certificate = new X509Certificate2(sslCertPath, _configFileProvider.SslCertPassword, X509KeyStorageFlags.DefaultKeySet);
|
|
||||||
}
|
|
||||||
catch (CryptographicException ex)
|
|
||||||
{
|
|
||||||
if (ex.HResult == 0x2 || ex.HResult == 0x2006D080)
|
|
||||||
{
|
|
||||||
throw new SonarrStartupException(ex, $"The SSL certificate file {sslCertPath} does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new SonarrStartupException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
configureOptions.ServerCertificate = certificate;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ConfigureKestrel(serverOptions =>
|
|
||||||
{
|
|
||||||
serverOptions.AllowSynchronousIO = true;
|
|
||||||
serverOptions.Limits.MaxRequestBodySize = null;
|
|
||||||
})
|
|
||||||
.ConfigureLogging(logging =>
|
|
||||||
{
|
|
||||||
logging.AddProvider(new NLogLoggerProvider());
|
|
||||||
logging.SetMinimumLevel(LogLevel.Warning);
|
|
||||||
})
|
|
||||||
.ConfigureServices(services =>
|
|
||||||
{
|
|
||||||
services
|
|
||||||
.AddSignalR()
|
|
||||||
.AddJsonProtocol(options =>
|
|
||||||
{
|
|
||||||
options.PayloadSerializerOptions = STJson.GetSerializerSettings();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Configure(app =>
|
|
||||||
{
|
|
||||||
app.UseRouting();
|
|
||||||
app.Properties["host.AppName"] = BuildInfo.AppName;
|
|
||||||
app.UsePathBase(_configFileProvider.UrlBase);
|
|
||||||
|
|
||||||
foreach (var middleWare in _middlewares.OrderBy(c => c.Order))
|
|
||||||
{
|
|
||||||
_logger.Debug("Attaching {0} to host", middleWare.GetType().Name);
|
|
||||||
middleWare.Attach(app);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_logger.Info("Listening on the following URLs:");
|
|
||||||
|
|
||||||
foreach (var url in urls)
|
|
||||||
{
|
|
||||||
_logger.Info(" {0}", url);
|
|
||||||
}
|
|
||||||
|
|
||||||
_host.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void StopServer()
|
|
||||||
{
|
|
||||||
_logger.Info("Attempting to stop OWIN host");
|
|
||||||
|
|
||||||
await _host.StopAsync(TimeSpan.FromSeconds(5));
|
|
||||||
_host.Dispose();
|
|
||||||
_host = null;
|
|
||||||
|
|
||||||
_logger.Info("Host has stopped");
|
|
||||||
}
|
|
||||||
|
|
||||||
private string BuildUrl(string scheme, string bindAddress, int port)
|
|
||||||
{
|
|
||||||
return $"{scheme}://{bindAddress}:{port}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using NzbDrone.Common.Composition;
|
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
using NzbDrone.Common.Http.Dispatchers;
|
|
||||||
|
|
||||||
namespace NzbDrone.Update
|
|
||||||
{
|
|
||||||
public class UpdateContainerBuilder : ContainerBuilderBase
|
|
||||||
{
|
|
||||||
private UpdateContainerBuilder(IStartupContext startupContext, List<string> assemblies)
|
|
||||||
: base(startupContext, assemblies)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IContainer Build(IStartupContext startupContext)
|
|
||||||
{
|
|
||||||
var assemblies = new List<string>
|
|
||||||
{
|
|
||||||
"Sonarr.Update"
|
|
||||||
};
|
|
||||||
|
|
||||||
return new UpdateContainerBuilder(startupContext, assemblies).Container;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,43 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NzbDrone.Core.Blocklisting;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using Sonarr.Http;
|
||||||
|
using Sonarr.Http.Extensions;
|
||||||
|
using Sonarr.Http.REST.Attributes;
|
||||||
|
|
||||||
|
namespace Sonarr.Api.V3.Blocklist
|
||||||
|
{
|
||||||
|
[V3ApiController]
|
||||||
|
public class BlocklistController : Controller
|
||||||
|
{
|
||||||
|
private readonly IBlocklistService _blocklistService;
|
||||||
|
|
||||||
|
public BlocklistController(IBlocklistService blocklistService)
|
||||||
|
{
|
||||||
|
_blocklistService = blocklistService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public PagingResource<BlocklistResource> GetBlocklist()
|
||||||
|
{
|
||||||
|
var pagingResource = Request.ReadPagingResourceFromRequest<BlocklistResource>();
|
||||||
|
var pagingSpec = pagingResource.MapToPagingSpec<BlocklistResource, NzbDrone.Core.Blocklisting.Blocklist>("date", SortDirection.Descending);
|
||||||
|
|
||||||
|
return pagingSpec.ApplyToPage(_blocklistService.Paged, model => BlocklistResourceMapper.MapToResource(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
[RestDeleteById]
|
||||||
|
public void DeleteBlocklist(int id)
|
||||||
|
{
|
||||||
|
_blocklistService.Delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("bulk")]
|
||||||
|
public object Remove([FromBody] BlocklistBulkResource resource)
|
||||||
|
{
|
||||||
|
_blocklistService.Delete(resource.Ids);
|
||||||
|
|
||||||
|
return new { };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NzbDrone.Core.DecisionEngine.Specifications;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.SignalR;
|
||||||
|
using Sonarr.Api.V3.Episodes;
|
||||||
|
using Sonarr.Http;
|
||||||
|
|
||||||
|
namespace Sonarr.Api.V3.Calendar
|
||||||
|
{
|
||||||
|
[V3ApiController]
|
||||||
|
public class CalendarController : EpisodeControllerWithSignalR
|
||||||
|
{
|
||||||
|
public CalendarController(IBroadcastSignalRMessage signalR,
|
||||||
|
IEpisodeService episodeService,
|
||||||
|
ISeriesService seriesService,
|
||||||
|
IUpgradableSpecification qualityUpgradableSpecification)
|
||||||
|
: base(episodeService, seriesService, qualityUpgradableSpecification, signalR)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public List<EpisodeResource> GetCalendar(DateTime? start, DateTime? end, bool unmonitored = false, bool includeSeries = false, bool includeEpisodeFile = false, bool includeEpisodeImages = false)
|
||||||
|
{
|
||||||
|
var startUse = start ?? DateTime.Today;
|
||||||
|
var endUse = end ?? DateTime.Today.AddDays(2);
|
||||||
|
|
||||||
|
var resources = MapToResource(_episodeService.EpisodesBetweenDates(startUse, endUse, unmonitored), includeSeries, includeEpisodeFile, includeEpisodeImages);
|
||||||
|
|
||||||
|
return resources.OrderBy(e => e.AirDateUtc).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,51 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using NzbDrone.Core.DecisionEngine;
|
|
||||||
using NzbDrone.Core.DecisionEngine.Specifications;
|
|
||||||
using NzbDrone.Core.Tv;
|
|
||||||
using NzbDrone.SignalR;
|
|
||||||
using Sonarr.Api.V3.Episodes;
|
|
||||||
using Sonarr.Http.Extensions;
|
|
||||||
|
|
||||||
namespace Sonarr.Api.V3.Calendar
|
|
||||||
{
|
|
||||||
public class CalendarModule : EpisodeModuleWithSignalR
|
|
||||||
{
|
|
||||||
public CalendarModule(IEpisodeService episodeService,
|
|
||||||
ISeriesService seriesService,
|
|
||||||
IUpgradableSpecification ugradableSpecification,
|
|
||||||
IBroadcastSignalRMessage signalRBroadcaster)
|
|
||||||
: base(episodeService, seriesService, ugradableSpecification, signalRBroadcaster, "calendar")
|
|
||||||
{
|
|
||||||
GetResourceAll = GetCalendar;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<EpisodeResource> GetCalendar()
|
|
||||||
{
|
|
||||||
var start = DateTime.Today;
|
|
||||||
var end = DateTime.Today.AddDays(2);
|
|
||||||
var includeUnmonitored = Request.GetBooleanQueryParameter("unmonitored");
|
|
||||||
var includeSeries = Request.GetBooleanQueryParameter("includeSeries");
|
|
||||||
var includeEpisodeFile = Request.GetBooleanQueryParameter("includeEpisodeFile");
|
|
||||||
var includeEpisodeImages = Request.GetBooleanQueryParameter("includeEpisodeImages");
|
|
||||||
|
|
||||||
var queryStart = Request.Query.Start;
|
|
||||||
var queryEnd = Request.Query.End;
|
|
||||||
|
|
||||||
if (queryStart.HasValue)
|
|
||||||
{
|
|
||||||
start = DateTime.Parse(queryStart.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queryEnd.HasValue)
|
|
||||||
{
|
|
||||||
end = DateTime.Parse(queryEnd.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
var resources = MapToResource(_episodeService.EpisodesBetweenDates(start, end, includeUnmonitored), includeSeries, includeEpisodeFile, includeEpisodeImages);
|
|
||||||
|
|
||||||
return resources.OrderBy(e => e.AirDateUtc).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,48 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using Sonarr.Http.REST;
|
||||||
|
using Sonarr.Http.REST.Attributes;
|
||||||
|
|
||||||
|
namespace Sonarr.Api.V3.Config
|
||||||
|
{
|
||||||
|
public abstract class ConfigController<TResource> : RestController<TResource>
|
||||||
|
where TResource : RestResource, new()
|
||||||
|
{
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
|
||||||
|
protected ConfigController(IConfigService configService)
|
||||||
|
{
|
||||||
|
_configService = configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TResource GetResourceById(int id)
|
||||||
|
{
|
||||||
|
return GetConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public TResource GetConfig()
|
||||||
|
{
|
||||||
|
var resource = ToResource(_configService);
|
||||||
|
resource.Id = 1;
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RestPutById]
|
||||||
|
public ActionResult<TResource> SaveConfig(TResource resource)
|
||||||
|
{
|
||||||
|
var dictionary = resource.GetType()
|
||||||
|
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||||
|
.ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null));
|
||||||
|
|
||||||
|
_configService.SaveConfigDictionary(dictionary);
|
||||||
|
|
||||||
|
return Accepted(resource.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract TResource ToResource(IConfigService model);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
using NzbDrone.Core.Validation.Paths;
|
||||||
|
using Sonarr.Http;
|
||||||
|
|
||||||
|
namespace Sonarr.Api.V3.Config
|
||||||
|
{
|
||||||
|
[V3ApiController("config/mediamanagement")]
|
||||||
|
public class MediaManagementConfigController : ConfigController<MediaManagementConfigResource>
|
||||||
|
{
|
||||||
|
public MediaManagementConfigController(IConfigService configService,
|
||||||
|
PathExistsValidator pathExistsValidator,
|
||||||
|
FolderChmodValidator folderChmodValidator,
|
||||||
|
FolderWritableValidator folderWritableValidator,
|
||||||
|
SeriesPathValidator seriesPathValidator,
|
||||||
|
StartupFolderValidator startupFolderValidator,
|
||||||
|
SystemFolderValidator systemFolderValidator,
|
||||||
|
RootFolderAncestorValidator rootFolderAncestorValidator,
|
||||||
|
RootFolderValidator rootFolderValidator)
|
||||||
|
: base(configService)
|
||||||
|
{
|
||||||
|
SharedValidator.RuleFor(c => c.RecycleBinCleanupDays).GreaterThanOrEqualTo(0);
|
||||||
|
SharedValidator.RuleFor(c => c.ChmodFolder).SetValidator(folderChmodValidator).When(c => !string.IsNullOrEmpty(c.ChmodFolder) && (OsInfo.IsLinux || OsInfo.IsOsx));
|
||||||
|
|
||||||
|
SharedValidator.RuleFor(c => c.RecycleBin).IsValidPath()
|
||||||
|
.SetValidator(folderWritableValidator)
|
||||||
|
.SetValidator(rootFolderValidator)
|
||||||
|
.SetValidator(pathExistsValidator)
|
||||||
|
.SetValidator(rootFolderAncestorValidator)
|
||||||
|
.SetValidator(startupFolderValidator)
|
||||||
|
.SetValidator(systemFolderValidator)
|
||||||
|
.SetValidator(seriesPathValidator)
|
||||||
|
.When(c => !string.IsNullOrWhiteSpace(c.RecycleBin));
|
||||||
|
|
||||||
|
SharedValidator.RuleFor(c => c.MinimumFreeSpaceWhenImporting).GreaterThanOrEqualTo(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MediaManagementConfigResource ToResource(IConfigService model)
|
||||||
|
{
|
||||||
|
return MediaManagementConfigResourceMapper.ToResource(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Validation;
|
|
||||||
using NzbDrone.Core.Validation.Paths;
|
|
||||||
|
|
||||||
namespace Sonarr.Api.V3.Config
|
|
||||||
{
|
|
||||||
public class MediaManagementConfigModule : SonarrConfigModule<MediaManagementConfigResource>
|
|
||||||
{
|
|
||||||
public MediaManagementConfigModule(IConfigService configService, PathExistsValidator pathExistsValidator, FolderChmodValidator folderChmodValidator)
|
|
||||||
: base(configService)
|
|
||||||
{
|
|
||||||
SharedValidator.RuleFor(c => c.RecycleBinCleanupDays).GreaterThanOrEqualTo(0);
|
|
||||||
SharedValidator.RuleFor(c => c.ChmodFolder).SetValidator(folderChmodValidator).When(c => !string.IsNullOrEmpty(c.ChmodFolder) && OsInfo.IsNotWindows);
|
|
||||||
SharedValidator.RuleFor(c => c.RecycleBin).IsValidPath().SetValidator(pathExistsValidator).When(c => !string.IsNullOrWhiteSpace(c.RecycleBin));
|
|
||||||
SharedValidator.RuleFor(c => c.MinimumFreeSpaceWhenImporting).GreaterThanOrEqualTo(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override MediaManagementConfigResource ToResource(IConfigService model)
|
|
||||||
{
|
|
||||||
return MediaManagementConfigResourceMapper.ToResource(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,52 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NzbDrone.Core.CustomFilters;
|
||||||
|
using Sonarr.Http;
|
||||||
|
using Sonarr.Http.REST;
|
||||||
|
using Sonarr.Http.REST.Attributes;
|
||||||
|
|
||||||
|
namespace Sonarr.Api.V3.CustomFilters
|
||||||
|
{
|
||||||
|
[V3ApiController]
|
||||||
|
public class CustomFilterController : RestController<CustomFilterResource>
|
||||||
|
{
|
||||||
|
private readonly ICustomFilterService _customFilterService;
|
||||||
|
|
||||||
|
public CustomFilterController(ICustomFilterService customFilterService)
|
||||||
|
{
|
||||||
|
_customFilterService = customFilterService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override CustomFilterResource GetResourceById(int id)
|
||||||
|
{
|
||||||
|
return _customFilterService.Get(id).ToResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public List<CustomFilterResource> GetCustomFilters()
|
||||||
|
{
|
||||||
|
return _customFilterService.All().ToResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RestPostById]
|
||||||
|
public ActionResult<CustomFilterResource> AddCustomFilter(CustomFilterResource resource)
|
||||||
|
{
|
||||||
|
var customFilter = _customFilterService.Add(resource.ToModel());
|
||||||
|
|
||||||
|
return Created(customFilter.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RestPutById]
|
||||||
|
public ActionResult<CustomFilterResource> UpdateCustomFilter(CustomFilterResource resource)
|
||||||
|
{
|
||||||
|
_customFilterService.Update(resource.ToModel());
|
||||||
|
return Accepted(resource.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RestDeleteById]
|
||||||
|
public void DeleteCustomResource(int id)
|
||||||
|
{
|
||||||
|
_customFilterService.Delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue