(cherry picked from commit d6170dbfedf27a6218afe242a0fae2eb8b368aec)pull/2397/head
parent
c247d07e84
commit
7fe36a7e92
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Lidarr.Http.Middleware
|
||||
{
|
||||
public class BufferingMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly List<string> _urls;
|
||||
|
||||
public BufferingMiddleware(RequestDelegate next, List<string> urls)
|
||||
{
|
||||
_next = next;
|
||||
_urls = urls;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
if (_urls.Any(p => context.Request.Path.StartsWithSegments(p, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
context.Request.EnableBuffering();
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Composition
|
||||
{
|
||||
public class AssemblyLoader
|
||||
{
|
||||
static AssemblyLoader()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ContainerResolveEventHandler);
|
||||
RegisterSQLiteResolver();
|
||||
}
|
||||
|
||||
public static IEnumerable<Assembly> Load(IEnumerable<string> assemblies)
|
||||
{
|
||||
var toLoad = assemblies.ToList();
|
||||
toLoad.Add("Lidarr.Common");
|
||||
toLoad.Add(OsInfo.IsWindows ? "Lidarr.Windows" : "Lidarr.Mono");
|
||||
|
||||
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 RegisterSQLiteResolver()
|
||||
{
|
||||
// This ensures we look for sqlite3 using libsqlite3.so.0 on Linux and not libsqlite3.so which
|
||||
// is less likely to exist.
|
||||
var sqliteAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "System.Data.SQLite.dll"));
|
||||
|
||||
try
|
||||
{
|
||||
NativeLibrary.SetDllImportResolver(sqliteAssembly, LoadSqliteNativeLib);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// This can only be set once per assembly
|
||||
// Catch required for NzbDrone.Host tests
|
||||
}
|
||||
}
|
||||
|
||||
private static IntPtr LoadSqliteNativeLib(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)
|
||||
{
|
||||
var mappedName = OsInfo.IsLinux && libraryName == "sqlite3" ? "libsqlite3.so.0" : libraryName;
|
||||
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,119 +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 ? "Lidarr.Windows" : "Lidarr.Mono");
|
||||
assemblies.Add("Lidarr.Common");
|
||||
|
||||
var startupPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
|
||||
foreach (var assemblyName in assemblies)
|
||||
{
|
||||
_loadedTypes.AddRange(AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{assemblyName}.dll")).GetTypes());
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ContainerResolveEventHandler);
|
||||
RegisterSQLiteResolver();
|
||||
|
||||
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 RegisterSQLiteResolver()
|
||||
{
|
||||
// This ensures we look for sqlite3 using libsqlite3.so.0 on Linux and not libsqlite3.so which
|
||||
// is less likely to exist.
|
||||
var sqliteAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "System.Data.SQLite.dll"));
|
||||
|
||||
try
|
||||
{
|
||||
NativeLibrary.SetDllImportResolver(sqliteAssembly, LoadSqliteNativeLib);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// This can only be set once per assembly
|
||||
// Catch required for NzbDrone.Host tests
|
||||
}
|
||||
}
|
||||
|
||||
private static IntPtr LoadSqliteNativeLib(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)
|
||||
{
|
||||
var mappedName = OsInfo.IsLinux && libraryName == "sqlite3" ? "libsqlite3.so.0" : libraryName;
|
||||
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,37 @@
|
||||
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);
|
||||
|
||||
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,34 +0,0 @@
|
||||
using System;
|
||||
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()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
// Nothing to clean up
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
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;
|
||||
|
||||
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,134 +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;
|
||||
|
||||
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) => LogManager.Configuration = null;
|
||||
}
|
||||
|
||||
_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,39 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.SignalR;
|
||||
|
||||
namespace NzbDrone.Host
|
||||
{
|
||||
public class MainAppContainerBuilder : ContainerBuilderBase
|
||||
{
|
||||
public static IContainer BuildContainer(StartupContext args)
|
||||
{
|
||||
var assemblies = new List<string>
|
||||
{
|
||||
"Lidarr.Host",
|
||||
"Lidarr.Core",
|
||||
"Lidarr.SignalR",
|
||||
"Lidarr.Api.V1",
|
||||
"Lidarr.Http"
|
||||
};
|
||||
|
||||
return new MainAppContainerBuilder(args, assemblies).Container;
|
||||
}
|
||||
|
||||
private MainAppContainerBuilder(StartupContext args, List<string> assemblies)
|
||||
: base(args, assemblies)
|
||||
{
|
||||
AutoRegisterImplementations<MessageHub>();
|
||||
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
Container.Register<INzbDroneServiceFactory, NzbDroneServiceFactory>();
|
||||
}
|
||||
else
|
||||
{
|
||||
Container.Register<INzbDroneServiceFactory, DummyNzbDroneServiceFactory>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Lidarr.Api.V1.System;
|
||||
using Lidarr.Http;
|
||||
using Lidarr.Http.Authentication;
|
||||
using Lidarr.Http.ErrorManagement;
|
||||
using Lidarr.Http.Frontend;
|
||||
using Lidarr.Http.Middleware;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
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.AccessControl;
|
||||
using NzbDrone.Http.Authentication;
|
||||
using NzbDrone.SignalR;
|
||||
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("Lidarr.Http.Authentication", LogLevel.Information);
|
||||
b.AddNLog();
|
||||
});
|
||||
|
||||
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.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,
|
||||
LidarrErrorPipeline 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.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/v1/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,26 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using NzbDrone.Common.Composition;
|
||||
|
||||
namespace NzbDrone.Host
|
||||
{
|
||||
public class ControllerActivator : IControllerActivator
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
|
||||
public ControllerActivator(IContainer container)
|
||||
{
|
||||
_container = container;
|
||||
}
|
||||
|
||||
public object Create(ControllerContext context)
|
||||
{
|
||||
return _container.Resolve(context.ActionDescriptor.ControllerTypeInfo.AsType());
|
||||
}
|
||||
|
||||
public void Release(ControllerContext context, object controller)
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
}
|
||||
}
|
@ -1,272 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Lidarr.Api.V1.System;
|
||||
using Lidarr.Http;
|
||||
using Lidarr.Http.Authentication;
|
||||
using Lidarr.Http.ErrorManagement;
|
||||
using Lidarr.Http.Frontend;
|
||||
using Lidarr.Http.Middleware;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Internal;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NLog;
|
||||
using NLog.Extensions.Logging;
|
||||
using NzbDrone.Common.Composition;
|
||||
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.SignalR;
|
||||
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
||||
|
||||
namespace NzbDrone.Host
|
||||
{
|
||||
public class WebHostController : IHostController
|
||||
{
|
||||
private readonly IContainer _container;
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IFirewallAdapter _firewallAdapter;
|
||||
private readonly LidarrErrorPipeline _errorHandler;
|
||||
private readonly Logger _logger;
|
||||
private IWebHost _host;
|
||||
|
||||
public WebHostController(IContainer container,
|
||||
IRuntimeInfo runtimeInfo,
|
||||
IConfigFileProvider configFileProvider,
|
||||
IFirewallAdapter firewallAdapter,
|
||||
LidarrErrorPipeline errorHandler,
|
||||
Logger logger)
|
||||
{
|
||||
_container = container;
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_configFileProvider = configFileProvider;
|
||||
_firewallAdapter = firewallAdapter;
|
||||
_errorHandler = errorHandler;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void StartServer()
|
||||
{
|
||||
if (OsInfo.IsWindows && _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 LidarrStartupException(ex, $"The SSL certificate file {sslCertPath} does not exist");
|
||||
}
|
||||
|
||||
throw new LidarrStartupException(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 =>
|
||||
{
|
||||
// So that we can resolve containers with our TinyIoC services
|
||||
services.AddSingleton(_container);
|
||||
services.AddSingleton<IControllerActivator, ControllerActivator>();
|
||||
|
||||
// Bits used in our custom middleware
|
||||
services.AddSingleton(_container.Resolve<LidarrErrorPipeline>());
|
||||
services.AddSingleton(_container.Resolve<ICacheableSpecification>());
|
||||
|
||||
// Used in authentication
|
||||
services.AddSingleton(_container.Resolve<IAuthenticationService>());
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
services
|
||||
.AddSignalR()
|
||||
.AddJsonProtocol(options =>
|
||||
{
|
||||
options.PayloadSerializerOptions = STJson.GetSerializerSettings();
|
||||
});
|
||||
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("UI", policy =>
|
||||
{
|
||||
policy.AuthenticationSchemes.Add(_configFileProvider.AuthenticationMethod.ToString());
|
||||
policy.RequireAuthenticatedUser();
|
||||
});
|
||||
|
||||
options.AddPolicy("SignalR", policy =>
|
||||
{
|
||||
policy.AuthenticationSchemes.Add("SignalR");
|
||||
policy.RequireAuthenticatedUser();
|
||||
});
|
||||
|
||||
// Require auth on everything except those marked [AllowAnonymous]
|
||||
options.DefaultPolicy = new AuthorizationPolicyBuilder("API")
|
||||
.RequireAuthenticatedUser()
|
||||
.Build();
|
||||
});
|
||||
|
||||
services.AddAppAuthentication(_configFileProvider);
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
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.Use((context, next) =>
|
||||
{
|
||||
if (context.Request.Path.StartsWithSegments("/api/v1/command", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
context.Request.EnableBuffering();
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
app.UseWebSockets();
|
||||
|
||||
app.UseEndpoints(x =>
|
||||
{
|
||||
x.MapHub<MessageHub>("/signalr/messages").RequireAuthorization("SignalR");
|
||||
x.MapControllers();
|
||||
});
|
||||
|
||||
// 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).
|
||||
_container.Register(app.ApplicationServices);
|
||||
_container.Register(app.ApplicationServices.GetService<IHubContext<MessageHub>>());
|
||||
_container.Register(app.ApplicationServices.GetService<IActionDescriptorCollectionProvider>());
|
||||
_container.Register(app.ApplicationServices.GetService<EndpointDataSource>());
|
||||
_container.Register(app.ApplicationServices.GetService<DfaGraphWriter>());
|
||||
})
|
||||
.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,24 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
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>
|
||||
{
|
||||
"Lidarr.Update"
|
||||
};
|
||||
|
||||
return new UpdateContainerBuilder(startupContext, assemblies).Container;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue