New: Use native dotnet host and DryIoc

(cherry picked from commit d6170dbfedf27a6218afe242a0fae2eb8b368aec)
pull/2397/head
ta264 3 years ago committed by Qstick
parent c247d07e84
commit 7fe36a7e92

@ -23,10 +23,7 @@ const requiresRestartKeys = [
'enableSsl', 'enableSsl',
'sslPort', 'sslPort',
'sslCertPath', 'sslCertPath',
'sslCertPassword', 'sslCertPassword'
'authenticationMethod',
'username',
'password'
]; ];
class GeneralSettings extends Component { class GeneralSettings extends Component {

@ -85,7 +85,6 @@ class SecuritySettings extends Component {
name="authenticationMethod" name="authenticationMethod"
values={authenticationMethodOptions} values={authenticationMethodOptions}
helpText="Require Username and Password to access Lidarr" helpText="Require Username and Password to access Lidarr"
helpTextWarning="Requires restart to take effect"
onChange={onInputChange} onChange={onInputChange}
{...authenticationMethod} {...authenticationMethod}
/> />
@ -99,7 +98,6 @@ class SecuritySettings extends Component {
<FormInputGroup <FormInputGroup
type={inputTypes.TEXT} type={inputTypes.TEXT}
name="username" name="username"
helpTextWarning="Requires restart to take effect"
onChange={onInputChange} onChange={onInputChange}
{...username} {...username}
/> />
@ -114,7 +112,6 @@ class SecuritySettings extends Component {
<FormInputGroup <FormInputGroup
type={inputTypes.PASSWORD} type={inputTypes.PASSWORD}
name="password" name="password"
helpTextWarning="Requires restart to take effect"
onChange={onInputChange} onChange={onInputChange}
{...password} {...password}
/> />

@ -8,6 +8,7 @@ using Lidarr.Http.REST.Attributes;
using Lidarr.Http.Validation; using Lidarr.Http.Validation;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.Composition;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Common.TPL; using NzbDrone.Common.TPL;
using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Datastore.Events;
@ -22,17 +23,17 @@ namespace Lidarr.Api.V1.Commands
public class CommandController : RestControllerWithSignalR<CommandResource, CommandModel>, IHandle<CommandUpdatedEvent> public class CommandController : RestControllerWithSignalR<CommandResource, CommandModel>, IHandle<CommandUpdatedEvent>
{ {
private readonly IManageCommandQueue _commandQueueManager; private readonly IManageCommandQueue _commandQueueManager;
private readonly IServiceFactory _serviceFactory; private readonly KnownTypes _knownTypes;
private readonly Debouncer _debouncer; private readonly Debouncer _debouncer;
private readonly Dictionary<int, CommandResource> _pendingUpdates; private readonly Dictionary<int, CommandResource> _pendingUpdates;
public CommandController(IManageCommandQueue commandQueueManager, public CommandController(IManageCommandQueue commandQueueManager,
IBroadcastSignalRMessage signalRBroadcaster, IBroadcastSignalRMessage signalRBroadcaster,
IServiceFactory serviceFactory) KnownTypes knownTypes)
: base(signalRBroadcaster) : base(signalRBroadcaster)
{ {
_commandQueueManager = commandQueueManager; _commandQueueManager = commandQueueManager;
_serviceFactory = serviceFactory; _knownTypes = knownTypes;
PostValidator.RuleFor(c => c.Name).NotBlank(); PostValidator.RuleFor(c => c.Name).NotBlank();
@ -49,7 +50,7 @@ namespace Lidarr.Api.V1.Commands
public ActionResult<CommandResource> StartCommand(CommandResource commandResource) public ActionResult<CommandResource> StartCommand(CommandResource commandResource)
{ {
var commandType = var commandType =
_serviceFactory.GetImplementations(typeof(Command)) _knownTypes.GetImplementations(typeof(Command))
.Single(c => c.Name.Replace("Command", "") .Single(c => c.Name.Replace("Command", "")
.Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase)); .Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));

@ -10,9 +10,9 @@ namespace Lidarr.Api.V1.ImportLists
{ {
public static readonly ImportListResourceMapper ResourceMapper = new ImportListResourceMapper(); public static readonly ImportListResourceMapper ResourceMapper = new ImportListResourceMapper();
public ImportListController(ImportListFactory importListFactory, public ImportListController(IImportListFactory importListFactory,
QualityProfileExistsValidator qualityProfileExistsValidator, QualityProfileExistsValidator qualityProfileExistsValidator,
MetadataProfileExistsValidator metadataProfileExistsValidator) MetadataProfileExistsValidator metadataProfileExistsValidator)
: base(importListFactory, "importlist", ResourceMapper) : base(importListFactory, "importlist", ResourceMapper)
{ {
Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.QualityProfileId)); Http.Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.QualityProfileId));

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NzbDrone.Core.Configuration;
namespace Lidarr.Http.Authentication namespace Lidarr.Http.Authentication
{ {
@ -17,17 +18,20 @@ namespace Lidarr.Http.Authentication
public string HeaderName { get; set; } public string HeaderName { get; set; }
public string QueryName { get; set; } public string QueryName { get; set; }
public string ApiKey { get; set; }
} }
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions> public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{ {
private readonly string _apiKey;
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options, public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options,
ILoggerFactory logger, ILoggerFactory logger,
UrlEncoder encoder, UrlEncoder encoder,
ISystemClock clock) ISystemClock clock,
IConfigFileProvider config)
: base(options, logger, encoder, clock) : base(options, logger, encoder, clock)
{ {
_apiKey = config.ApiKey;
} }
private string ParseApiKey() private string ParseApiKey()
@ -56,7 +60,7 @@ namespace Lidarr.Http.Authentication
return Task.FromResult(AuthenticateResult.NoResult()); return Task.FromResult(AuthenticateResult.NoResult());
} }
if (Options.ApiKey == providedApiKey) if (_apiKey == providedApiKey)
{ {
var claims = new List<Claim> var claims = new List<Claim>
{ {

@ -2,7 +2,6 @@ using System;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using NzbDrone.Core.Authentication; using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
namespace Lidarr.Http.Authentication namespace Lidarr.Http.Authentication
{ {
@ -13,53 +12,37 @@ namespace Lidarr.Http.Authentication
return authenticationBuilder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(name, options); return authenticationBuilder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(name, options);
} }
public static AuthenticationBuilder AddBasicAuthentication(this AuthenticationBuilder authenticationBuilder) public static AuthenticationBuilder AddBasic(this AuthenticationBuilder authenticationBuilder, string name)
{ {
return authenticationBuilder.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>(AuthenticationType.Basic.ToString(), options => { }); return authenticationBuilder.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>(name, options => { });
} }
public static AuthenticationBuilder AddNoAuthentication(this AuthenticationBuilder authenticationBuilder) public static AuthenticationBuilder AddNone(this AuthenticationBuilder authenticationBuilder, string name)
{ {
return authenticationBuilder.AddScheme<AuthenticationSchemeOptions, NoAuthenticationHandler>(AuthenticationType.None.ToString(), options => { }); return authenticationBuilder.AddScheme<AuthenticationSchemeOptions, NoAuthenticationHandler>(name, options => { });
} }
public static AuthenticationBuilder AddAppAuthentication(this IServiceCollection services, IConfigFileProvider config) public static AuthenticationBuilder AddAppAuthentication(this IServiceCollection services)
{ {
var authBuilder = services.AddAuthentication(config.AuthenticationMethod.ToString()); return services.AddAuthentication()
.AddNone(AuthenticationType.None.ToString())
if (config.AuthenticationMethod == AuthenticationType.Basic) .AddBasic(AuthenticationType.Basic.ToString())
{ .AddCookie(AuthenticationType.Forms.ToString(), options =>
authBuilder.AddBasicAuthentication();
}
else if (config.AuthenticationMethod == AuthenticationType.Forms)
{
authBuilder.AddCookie(AuthenticationType.Forms.ToString(), options =>
{ {
options.AccessDeniedPath = "/login?loginFailed=true"; options.AccessDeniedPath = "/login?loginFailed=true";
options.LoginPath = "/login"; options.LoginPath = "/login";
options.ExpireTimeSpan = TimeSpan.FromDays(7); options.ExpireTimeSpan = TimeSpan.FromDays(7);
})
.AddApiKey("API", options =>
{
options.HeaderName = "X-Api-Key";
options.QueryName = "apikey";
})
.AddApiKey("SignalR", options =>
{
options.HeaderName = "X-Api-Key";
options.QueryName = "access_token";
}); });
}
else
{
authBuilder.AddNoAuthentication();
}
authBuilder.AddApiKey("API", options =>
{
options.HeaderName = "X-Api-Key";
options.QueryName = "apikey";
options.ApiKey = config.ApiKey;
});
authBuilder.AddApiKey("SignalR", options =>
{
options.HeaderName = "X-Api-Key";
options.QueryName = "access_token";
options.ApiKey = config.ApiKey;
});
return authBuilder;
} }
} }
} }

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
namespace Lidarr.Http.Authentication namespace Lidarr.Http.Authentication
@ -35,14 +36,14 @@ namespace Lidarr.Http.Authentication
{ {
new Claim("user", user.Username), new Claim("user", user.Username),
new Claim("identifier", user.Identifier.ToString()), new Claim("identifier", user.Identifier.ToString()),
new Claim("UiAuth", "true") new Claim("AuthType", AuthenticationType.Forms.ToString())
}; };
var authProperties = new AuthenticationProperties var authProperties = new AuthenticationProperties
{ {
IsPersistent = resource.RememberMe == "on" IsPersistent = resource.RememberMe == "on"
}; };
await HttpContext.SignInAsync(new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookies", "user", "identifier")), authProperties); await HttpContext.SignInAsync(AuthenticationType.Forms.ToString(), new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookies", "user", "identifier")), authProperties);
return Redirect("/"); return Redirect("/");
} }
@ -51,7 +52,7 @@ namespace Lidarr.Http.Authentication
public async Task<IActionResult> Logout() public async Task<IActionResult> Logout()
{ {
_authService.Logout(HttpContext); _authService.Logout(HttpContext);
await HttpContext.SignOutAsync(); await HttpContext.SignOutAsync(AuthenticationType.Forms.ToString());
return Redirect("/"); return Redirect("/");
} }
} }

@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Authentication;
namespace Lidarr.Http.Authentication namespace Lidarr.Http.Authentication
{ {
@ -58,7 +59,7 @@ namespace Lidarr.Http.Authentication
{ {
new Claim("user", user.Username), new Claim("user", user.Username),
new Claim("identifier", user.Identifier.ToString()), new Claim("identifier", user.Identifier.ToString()),
new Claim("UiAuth", "true") new Claim("AuthType", AuthenticationType.Basic.ToString())
}; };
var identity = new ClaimsIdentity(claims, "Basic", "user", "identifier"); var identity = new ClaimsIdentity(claims, "Basic", "user", "identifier");

@ -5,13 +5,13 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NzbDrone.Core.Authentication;
namespace Lidarr.Http.Authentication namespace Lidarr.Http.Authentication
{ {
public class NoAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> public class NoAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{ {
public NoAuthenticationHandler(IAuthenticationService authService, public NoAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, ILoggerFactory logger,
UrlEncoder encoder, UrlEncoder encoder,
ISystemClock clock) ISystemClock clock)
@ -24,7 +24,7 @@ namespace Lidarr.Http.Authentication
var claims = new List<Claim> var claims = new List<Claim>
{ {
new Claim("user", "Anonymous"), new Claim("user", "Anonymous"),
new Claim("UiAuth", "true") new Claim("AuthType", AuthenticationType.None.ToString())
}; };
var identity = new ClaimsIdentity(claims, "NoAuth", "user", "identifier"); var identity = new ClaimsIdentity(claims, "NoAuth", "user", "identifier");

@ -10,13 +10,13 @@ namespace Lidarr.Http.Frontend.Mappers
public abstract class HtmlMapperBase : StaticResourceMapperBase public abstract class HtmlMapperBase : StaticResourceMapperBase
{ {
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory; private readonly Lazy<ICacheBreakerProvider> _cacheBreakProviderFactory;
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics|svg|json))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics|svg|json))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private string _generatedContent; private string _generatedContent;
protected HtmlMapperBase(IDiskProvider diskProvider, protected HtmlMapperBase(IDiskProvider diskProvider,
Func<ICacheBreakerProvider> cacheBreakProviderFactory, Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
Logger logger) Logger logger)
: base(diskProvider, logger) : base(diskProvider, logger)
{ {
@ -56,7 +56,7 @@ namespace Lidarr.Http.Frontend.Mappers
} }
var text = _diskProvider.ReadAllText(HtmlPath); var text = _diskProvider.ReadAllText(HtmlPath);
var cacheBreakProvider = _cacheBreakProviderFactory(); var cacheBreakProvider = _cacheBreakProviderFactory.Value;
text = ReplaceRegex.Replace(text, match => text = ReplaceRegex.Replace(text, match =>
{ {

@ -14,7 +14,7 @@ namespace Lidarr.Http.Frontend.Mappers
public IndexHtmlMapper(IAppFolderInfo appFolderInfo, public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
Func<ICacheBreakerProvider> cacheBreakProviderFactory, Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
Logger logger) Logger logger)
: base(diskProvider, cacheBreakProviderFactory, logger) : base(diskProvider, cacheBreakProviderFactory, logger)
{ {

@ -11,7 +11,7 @@ namespace Lidarr.Http.Frontend.Mappers
{ {
public LoginHtmlMapper(IAppFolderInfo appFolderInfo, public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider, IDiskProvider diskProvider,
Func<ICacheBreakerProvider> cacheBreakProviderFactory, Lazy<ICacheBreakerProvider> cacheBreakProviderFactory,
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
Logger logger) Logger logger)
: base(diskProvider, cacheBreakProviderFactory, logger) : base(diskProvider, cacheBreakProviderFactory, logger)

@ -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);
}
}
}

@ -1,4 +1,4 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
@ -21,7 +21,6 @@ namespace NzbDrone.Common.Test.InstrumentationTests
private static Exception[] FilteredExceptions = new Exception[] private static Exception[] FilteredExceptions = new Exception[]
{ {
new UnauthorizedAccessException(), new UnauthorizedAccessException(),
new TinyIoC.TinyIoCResolutionException(typeof(string)),
new OutOfMemoryException() new OutOfMemoryException()
}; };

@ -1,8 +1,13 @@
using System.Linq; using System.Linq;
using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Datastore; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Host; using NzbDrone.Host;
@ -16,11 +21,16 @@ namespace NzbDrone.Common.Test
[Test] [Test]
public void event_handlers_should_be_unique() public void event_handlers_should_be_unique()
{ {
var container = MainAppContainerBuilder.BuildContainer(new StartupContext()); var container = new Container(rules => rules.WithNzbDroneRules())
container.Register<IMainDatabase>(new MainDatabase(null)); .AddNzbDroneLogger()
container.Resolve<IAppFolderFactory>().Register(); .AutoAddServices(Bootstrap.ASSEMBLIES)
.AddDummyDatabase()
.AddStartupContext(new StartupContext("first", "second"))
.GetServiceProvider();
Mocker.SetConstant(container); container.GetRequiredService<IAppFolderFactory>().Register();
Mocker.SetConstant<System.IServiceProvider>(container);
var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>() var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>()
.Select(c => c.GetType().FullName); .Select(c => c.GetType().FullName);

@ -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,113 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TinyIoC;
namespace NzbDrone.Common.Composition
{
public class Container : IContainer
{
private readonly TinyIoCContainer _container;
private readonly List<Type> _loadedTypes;
public Container(TinyIoCContainer container, List<Type> loadedTypes)
{
_container = container;
_loadedTypes = loadedTypes;
_container.Register<IContainer>(this);
}
public void Register<TService, TImplementation>()
where TImplementation : class, TService
where TService : class
{
_container.Register<TService, TImplementation>();
}
public void Register<T>(T instance)
where T : class
{
_container.Register<T>(instance);
}
public T Resolve<T>()
where T : class
{
return _container.Resolve<T>();
}
public object Resolve(Type type)
{
return _container.Resolve(type);
}
public void Register(Type serviceType, Type implementationType)
{
_container.Register(serviceType, implementationType);
}
public void Register<TService>(Func<IContainer, TService> factory)
where TService : class
{
_container.Register((c, n) => factory(this));
}
public void RegisterSingleton(Type service, Type implementation)
{
var factory = CreateSingletonImplementationFactory(implementation);
// For Resolve and ResolveAll
_container.Register(service, factory);
// For ctor(IEnumerable<T>)
var enumerableType = typeof(IEnumerable<>).MakeGenericType(service);
_container.Register(enumerableType, (c, p) =>
{
var instance = factory(c, p);
var result = Array.CreateInstance(service, 1);
result.SetValue(instance, 0);
return result;
});
}
public IEnumerable<T> ResolveAll<T>()
where T : class
{
return _container.ResolveAll<T>();
}
public void RegisterAllAsSingleton(Type service, IEnumerable<Type> implementationList)
{
foreach (var implementation in implementationList)
{
var factory = CreateSingletonImplementationFactory(implementation);
// For ResolveAll and ctor(IEnumerable<T>)
_container.Register(service, factory, implementation.FullName);
}
}
private Func<TinyIoCContainer, NamedParameterOverloads, object> CreateSingletonImplementationFactory(Type implementation)
{
const string singleImplPrefix = "singleImpl_";
_container.Register(implementation, implementation, singleImplPrefix + implementation.FullName).AsSingleton();
return (c, p) => _container.Resolve(implementation, singleImplPrefix + implementation.FullName);
}
public bool IsTypeRegistered(Type type)
{
return _container.CanResolve(type);
}
public IEnumerable<Type> GetImplementations(Type contractType)
{
return _loadedTypes
.Where(implementation =>
contractType.IsAssignableFrom(implementation) &&
!implementation.IsInterface &&
!implementation.IsAbstract);
}
}
}

@ -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;
}
}
}

@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Common.Composition
{
public interface IContainer
{
void Register<T>(T instance)
where T : class;
void Register<TService, TImplementation>()
where TImplementation : class, TService
where TService : class;
T Resolve<T>()
where T : class;
object Resolve(Type type);
void Register(Type serviceType, Type implementationType);
void Register<TService>(Func<IContainer, TService> factory)
where TService : class;
void RegisterSingleton(Type service, Type implementation);
IEnumerable<T> ResolveAll<T>()
where T : class;
void RegisterAllAsSingleton(Type registrationType, IEnumerable<Type> implementationList);
bool IsTypeRegistered(Type type);
IEnumerable<Type> GetImplementations(Type contractType);
}
}

@ -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;
}
}
}

@ -5,6 +5,8 @@
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants> <DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DryIoc.dll" Version="4.7.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="DotNet4.SocksProxy" Version="1.4.0.1" /> <PackageReference Include="DotNet4.SocksProxy" Version="1.4.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="4.7.9" /> <PackageReference Include="NLog" Version="4.7.9" />

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Common.Composition; using Microsoft.Extensions.DependencyInjection;
namespace NzbDrone.Common namespace NzbDrone.Common
{ {
@ -17,9 +17,9 @@ namespace NzbDrone.Common
public class ServiceFactory : IServiceFactory public class ServiceFactory : IServiceFactory
{ {
private readonly IContainer _container; private readonly System.IServiceProvider _container;
public ServiceFactory(IContainer container) public ServiceFactory(System.IServiceProvider container)
{ {
_container = container; _container = container;
} }
@ -27,23 +27,23 @@ namespace NzbDrone.Common
public T Build<T>() public T Build<T>()
where T : class where T : class
{ {
return _container.Resolve<T>(); return _container.GetRequiredService<T>();
} }
public IEnumerable<T> BuildAll<T>() public IEnumerable<T> BuildAll<T>()
where T : class where T : class
{ {
return _container.ResolveAll<T>().GroupBy(c => c.GetType().FullName).Select(g => g.First()); return _container.GetServices<T>().GroupBy(c => c.GetType().FullName).Select(g => g.First());
} }
public object Build(Type contract) public object Build(Type contract)
{ {
return _container.Resolve(contract); return _container.GetRequiredService(contract);
} }
public IEnumerable<Type> GetImplementations(Type contract) public IEnumerable<Type> GetImplementations(Type contract)
{ {
return _container.GetImplementations(contract); return _container.GetServices(contract).Select(x => x.GetType());
} }
} }
} }

File diff suppressed because it is too large Load Diff

@ -1,15 +0,0 @@
using NzbDrone.Host;
namespace NzbDrone.Console
{
public class ConsoleAlerts : IUserAlert
{
public void Alert(string message)
{
System.Console.WriteLine();
System.Console.WriteLine(message);
System.Console.WriteLine("Press enter to continue");
System.Console.ReadLine();
}
}
}

@ -2,6 +2,8 @@ using System;
using System.IO; using System.IO;
using System.Net.Sockets; using System.Net.Sockets;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using NLog; using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Exceptions; using NzbDrone.Common.Exceptions;
@ -15,7 +17,7 @@ namespace NzbDrone.Console
{ {
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(ConsoleApp)); private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(ConsoleApp));
private enum ExitCodes : int private enum ExitCodes
{ {
Normal = 0, Normal = 0,
UnknownFailure = 1, UnknownFailure = 1,
@ -40,7 +42,7 @@ namespace NzbDrone.Console
throw; throw;
} }
Bootstrap.Start(startupArgs, new ConsoleAlerts()); Bootstrap.Start(args);
} }
catch (LidarrStartupException ex) catch (LidarrStartupException ex)
{ {

@ -1,6 +1,7 @@
using System; using System;
using FluentMigrator; using FluentMigrator;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NLog.Extensions.Logging;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Core.Datastore.Migration.Framework; using NzbDrone.Core.Datastore.Migration.Framework;
@ -39,7 +40,7 @@ namespace NzbDrone.Core.Test.Framework
protected override void SetupLogging() protected override void SetupLogging()
{ {
Mocker.SetConstant<ILoggerProvider>(Mocker.Resolve<MigrationLoggerProvider>()); Mocker.SetConstant<ILoggerProvider>(Mocker.Resolve<NLogLoggerProvider>());
} }
[SetUp] [SetUp]

@ -1,7 +1,6 @@
using System; using System;
using System.Data.SQLite; using System.Data.SQLite;
using NLog; using NLog;
using NzbDrone.Common.Composition;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Exceptions; using NzbDrone.Common.Exceptions;
@ -39,17 +38,6 @@ namespace NzbDrone.Core.Datastore
Environment.SetEnvironmentVariable("No_PreLoadSQLite", "true"); Environment.SetEnvironmentVariable("No_PreLoadSQLite", "true");
} }
public static void RegisterDatabase(IContainer container)
{
var mainDb = new MainDatabase(container.Resolve<IDbFactory>().Create());
container.Register<IMainDatabase>(mainDb);
var logDb = new LogDatabase(container.Resolve<IDbFactory>().Create(MigrationType.Log));
container.Register<ILogDatabase>(logDb);
}
public DbFactory(IMigrationController migrationController, public DbFactory(IMigrationController migrationController,
IConnectionStringFactory connectionStringFactory, IConnectionStringFactory connectionStringFactory,
IDiskProvider diskProvider, IDiskProvider diskProvider,

@ -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,4 +1,4 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using FluentMigrator.Runner; using FluentMigrator.Runner;
@ -7,6 +7,7 @@ using FluentMigrator.Runner.Processors;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NLog; using NLog;
using NLog.Extensions.Logging;
namespace NzbDrone.Core.Datastore.Migration.Framework namespace NzbDrone.Core.Datastore.Migration.Framework
{ {
@ -34,7 +35,7 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
_logger.Info("*** Migrating {0} ***", connectionString); _logger.Info("*** Migrating {0} ***", connectionString);
var serviceProvider = new ServiceCollection() var serviceProvider = new ServiceCollection()
.AddLogging(lb => lb.AddProvider(_migrationLoggerProvider)) .AddLogging(b => b.AddNLog())
.AddFluentMigratorCore() .AddFluentMigratorCore()
.ConfigureRunner( .ConfigureRunner(
builder => builder builder => builder

@ -1,59 +0,0 @@
using System;
using FluentMigrator.Runner;
using FluentMigrator.Runner.Logging;
using NLog;
namespace NzbDrone.Core.Datastore.Migration.Framework
{
public class MigrationLogger : FluentMigratorLogger
{
private readonly Logger _logger;
public MigrationLogger(Logger logger,
FluentMigratorLoggerOptions options)
: base(options)
{
_logger = logger;
}
protected override void WriteHeading(string message)
{
_logger.Info("*** {0} ***", message);
}
protected override void WriteSay(string message)
{
_logger.Debug(message);
}
protected override void WriteEmphasize(string message)
{
_logger.Warn(message);
}
protected override void WriteSql(string sql)
{
_logger.Debug(sql);
}
protected override void WriteEmptySql()
{
_logger.Debug(@"No SQL statement executed.");
}
protected override void WriteElapsedTime(TimeSpan timeSpan)
{
_logger.Debug("Took: {0}", timeSpan);
}
protected override void WriteError(string message)
{
_logger.Error(message);
}
protected override void WriteError(Exception exception)
{
_logger.Error(exception);
}
}
}

@ -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
}
}
}

@ -1,8 +1,8 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Composition;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
@ -21,7 +21,7 @@ namespace NzbDrone.Core.Download
public DownloadClientFactory(IDownloadClientStatusService downloadClientStatusService, public DownloadClientFactory(IDownloadClientStatusService downloadClientStatusService,
IDownloadClientRepository providerRepository, IDownloadClientRepository providerRepository,
IEnumerable<IDownloadClient> providers, IEnumerable<IDownloadClient> providers,
IContainer container, IServiceProvider container,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
: base(providerRepository, providers, container, eventAggregator, logger) : base(providerRepository, providers, container, eventAggregator, logger)

@ -19,7 +19,7 @@ namespace NzbDrone.Core.Extras
public ExistingExtraFileService(IDiskProvider diskProvider, public ExistingExtraFileService(IDiskProvider diskProvider,
IDiskScanService diskScanService, IDiskScanService diskScanService,
List<IImportExistingExtraFiles> existingExtraFileImporters, IEnumerable<IImportExistingExtraFiles> existingExtraFileImporters,
Logger logger) Logger logger)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;

@ -40,7 +40,7 @@ namespace NzbDrone.Core.Extras
ITrackService trackService, ITrackService trackService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IConfigService configService, IConfigService configService,
List<IManageExtraFiles> extraFileManagers, IEnumerable<IManageExtraFiles> extraFileManagers,
Logger logger) Logger logger)
{ {
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
@ -17,7 +17,7 @@ namespace NzbDrone.Core.Extras.Metadata
{ {
private readonly IMetadataRepository _providerRepository; private readonly IMetadataRepository _providerRepository;
public MetadataFactory(IMetadataRepository providerRepository, IEnumerable<IMetadata> providers, IContainer container, IEventAggregator eventAggregator, Logger logger) public MetadataFactory(IMetadataRepository providerRepository, IEnumerable<IMetadata> providers, IServiceProvider container, IEventAggregator eventAggregator, Logger logger)
: base(providerRepository, providers, container, eventAggregator, logger) : base(providerRepository, providers, container, eventAggregator, logger)
{ {
_providerRepository = providerRepository; _providerRepository = providerRepository;

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentValidation.Results; using FluentValidation.Results;
@ -21,7 +22,7 @@ namespace NzbDrone.Core.ImportLists
public ImportListFactory(IImportListStatusService importListStatusService, public ImportListFactory(IImportListStatusService importListStatusService,
IImportListRepository providerRepository, IImportListRepository providerRepository,
IEnumerable<IImportList> providers, IEnumerable<IImportList> providers,
IContainer container, IServiceProvider container,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
: base(providerRepository, providers, container, eventAggregator, logger) : base(providerRepository, providers, container, eventAggregator, logger)

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentValidation.Results; using FluentValidation.Results;
@ -23,7 +24,7 @@ namespace NzbDrone.Core.Indexers
public IndexerFactory(IIndexerStatusService indexerStatusService, public IndexerFactory(IIndexerStatusService indexerStatusService,
IIndexerRepository providerRepository, IIndexerRepository providerRepository,
IEnumerable<IIndexer> providers, IEnumerable<IIndexer> providers,
IContainer container, IServiceProvider container,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
: base(providerRepository, providers, container, eventAggregator, logger) : base(providerRepository, providers, container, eventAggregator, logger)

@ -6,12 +6,16 @@
<PackageReference Include="Dapper" Version="2.0.78" /> <PackageReference Include="Dapper" Version="2.0.78" />
<PackageReference Include="System.Text.Json" Version="5.0.2" /> <PackageReference Include="System.Text.Json" Version="5.0.2" />
<PackageReference Include="System.Memory" Version="4.5.4" /> <PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
<PackageReference Include="FluentMigrator.Runner" Version="4.0.0-alpha.268" /> <PackageReference Include="FluentMigrator.Runner" Version="4.0.0-alpha.268" />
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="4.0.0-alpha.268" /> <PackageReference Include="FluentMigrator.Runner.SQLite" Version="4.0.0-alpha.268" />
<PackageReference Include="FluentValidation" Version="8.6.2" /> <PackageReference Include="FluentValidation" Version="8.6.2" />
<PackageReference Include="MailKit" Version="2.11.1" /> <PackageReference Include="MailKit" Version="2.11.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="4.7.9" /> <PackageReference Include="NLog" Version="4.7.9" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.2" />
<PackageReference Include="RestSharp" Version="106.11.7" /> <PackageReference Include="RestSharp" Version="106.11.7" />
<PackageReference Include="System.IO.Abstractions" Version="13.2.29" /> <PackageReference Include="System.IO.Abstractions" Version="13.2.29" />
<PackageReference Include="TagLibSharp-Lidarr" Version="2.2.0.25" /> <PackageReference Include="TagLibSharp-Lidarr" Version="2.2.0.25" />

@ -5,6 +5,7 @@ using System.Net;
using System.Threading; using System.Threading;
using NLog; using NLog;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.Composition;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.Exceptions; using NzbDrone.Core.Exceptions;
@ -36,17 +37,18 @@ namespace NzbDrone.Core.Messaging.Commands
public class CommandQueueManager : IManageCommandQueue, IHandle<ApplicationStartedEvent> public class CommandQueueManager : IManageCommandQueue, IHandle<ApplicationStartedEvent>
{ {
private readonly ICommandRepository _repo; private readonly ICommandRepository _repo;
private readonly IServiceFactory _serviceFactory; private readonly KnownTypes _knownTypes;
private readonly Logger _logger; private readonly Logger _logger;
private readonly CommandQueue _commandQueue; private readonly CommandQueue _commandQueue;
public CommandQueueManager(ICommandRepository repo, public CommandQueueManager(ICommandRepository repo,
IServiceFactory serviceFactory, IServiceFactory serviceFactory,
KnownTypes knownTypes,
Logger logger) Logger logger)
{ {
_repo = repo; _repo = repo;
_serviceFactory = serviceFactory; _knownTypes = knownTypes;
_logger = logger; _logger = logger;
_commandQueue = new CommandQueue(); _commandQueue = new CommandQueue();
@ -233,9 +235,8 @@ namespace NzbDrone.Core.Messaging.Commands
private dynamic GetCommand(string commandName) private dynamic GetCommand(string commandName)
{ {
commandName = commandName.Split('.').Last(); commandName = commandName.Split('.').Last();
var commands = _knownTypes.GetImplementations(typeof(Command));
var commandType = _serviceFactory.GetImplementations(typeof(Command)) var commandType = commands.Single(c => c.Name.Equals(commandName, StringComparison.InvariantCultureIgnoreCase));
.Single(c => c.Name.Equals(commandName, StringComparison.InvariantCultureIgnoreCase));
return Json.Deserialize("{}", commandType); return Json.Deserialize("{}", commandType);
} }

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
@ -21,7 +22,7 @@ namespace NzbDrone.Core.Notifications
public class NotificationFactory : ProviderFactory<INotification, NotificationDefinition>, INotificationFactory public class NotificationFactory : ProviderFactory<INotification, NotificationDefinition>, INotificationFactory
{ {
public NotificationFactory(INotificationRepository providerRepository, IEnumerable<INotification> providers, IContainer container, IEventAggregator eventAggregator, Logger logger) public NotificationFactory(INotificationRepository providerRepository, IEnumerable<INotification> providers, IServiceProvider container, IEventAggregator eventAggregator, Logger logger)
: base(providerRepository, providers, container, eventAggregator, logger) : base(providerRepository, providers, container, eventAggregator, logger)
{ {
} }

@ -37,7 +37,7 @@ namespace NzbDrone.Core.Tags
public TagService(ITagRepository repo, public TagService(ITagRepository repo,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IDelayProfileService delayProfileService, IDelayProfileService delayProfileService,
ImportListFactory importListFactory, IImportListFactory importListFactory,
INotificationFactory notificationFactory, INotificationFactory notificationFactory,
IReleaseProfileService releaseProfileService, IReleaseProfileService releaseProfileService,
IArtistService artistService, IArtistService artistService,

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentValidation.Results; using FluentValidation.Results;
using Microsoft.Extensions.DependencyInjection;
using NLog; using NLog;
using NzbDrone.Common.Composition; using NzbDrone.Common.Composition;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
@ -15,7 +16,7 @@ namespace NzbDrone.Core.ThingiProvider
where TProvider : IProvider where TProvider : IProvider
{ {
private readonly IProviderRepository<TProviderDefinition> _providerRepository; private readonly IProviderRepository<TProviderDefinition> _providerRepository;
private readonly IContainer _container; private readonly IServiceProvider _container;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
@ -23,7 +24,7 @@ namespace NzbDrone.Core.ThingiProvider
protected ProviderFactory(IProviderRepository<TProviderDefinition> providerRepository, protected ProviderFactory(IProviderRepository<TProviderDefinition> providerRepository,
IEnumerable<TProvider> providers, IEnumerable<TProvider> providers,
IContainer container, IServiceProvider container,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
{ {
@ -123,7 +124,7 @@ namespace NzbDrone.Core.ThingiProvider
public TProvider GetInstance(TProviderDefinition definition) public TProvider GetInstance(TProviderDefinition definition)
{ {
var type = GetImplementation(definition); var type = GetImplementation(definition);
var instance = (TProvider)_container.Resolve(type); var instance = (TProvider)_container.GetRequiredService(type);
instance.Definition = definition; instance.Definition = definition;
SetProviderCharacteristics(instance, definition); SetProviderCharacteristics(instance, definition);
return instance; return instance;

@ -1,12 +1,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.Composition; using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Datastore; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
@ -15,44 +19,50 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Host; using NzbDrone.Host;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using NzbDrone.Test.Common; using NzbDrone.Test.Common;
using IServiceProvider = System.IServiceProvider;
namespace NzbDrone.App.Test namespace NzbDrone.App.Test
{ {
[TestFixture] [TestFixture]
public class ContainerFixture : TestBase public class ContainerFixture : TestBase
{ {
private IContainer _container; private IServiceProvider _container;
[SetUp] [SetUp]
public void SetUp() public void SetUp()
{ {
var args = new StartupContext("first", "second"); var args = new StartupContext("first", "second");
_container = MainAppContainerBuilder.BuildContainer(args);
_container.Register<IMainDatabase>(new MainDatabase(null));
// set up a dummy broadcaster to allow tests to resolve // set up a dummy broadcaster to allow tests to resolve
var mockBroadcaster = new Mock<IBroadcastSignalRMessage>(); var mockBroadcaster = new Mock<IBroadcastSignalRMessage>();
_container.Register<IBroadcastSignalRMessage>(mockBroadcaster.Object);
var container = new Container(rules => rules.WithNzbDroneRules())
.AutoAddServices(Bootstrap.ASSEMBLIES)
.AddNzbDroneLogger()
.AddDummyDatabase()
.AddStartupContext(args);
container.RegisterInstance<IBroadcastSignalRMessage>(mockBroadcaster.Object);
_container = container.GetServiceProvider();
} }
[Test] [Test]
public void should_be_able_to_resolve_indexers() public void should_be_able_to_resolve_indexers()
{ {
_container.Resolve<IEnumerable<IIndexer>>().Should().NotBeEmpty(); _container.GetRequiredService<IEnumerable<IIndexer>>().Should().NotBeEmpty();
} }
[Test] [Test]
public void should_be_able_to_resolve_downloadclients() public void should_be_able_to_resolve_downloadclients()
{ {
_container.Resolve<IEnumerable<IDownloadClient>>().Should().NotBeEmpty(); _container.GetRequiredService<IEnumerable<IDownloadClient>>().Should().NotBeEmpty();
} }
[Test] [Test]
public void container_should_inject_itself() public void container_should_inject_itself()
{ {
var factory = _container.Resolve<IServiceFactory>(); var factory = _container.GetRequiredService<IServiceFactory>();
factory.Build<IIndexerFactory>().Should().NotBeNull(); factory.Build<IIndexerFactory>().Should().NotBeNull();
} }
@ -62,7 +72,7 @@ namespace NzbDrone.App.Test
{ {
var genericExecutor = typeof(IExecute<>).MakeGenericType(typeof(RssSyncCommand)); var genericExecutor = typeof(IExecute<>).MakeGenericType(typeof(RssSyncCommand));
var executor = _container.Resolve(genericExecutor); var executor = _container.GetRequiredService(genericExecutor);
executor.Should().NotBeNull(); executor.Should().NotBeNull();
executor.Should().BeAssignableTo<IExecute<RssSyncCommand>>(); executor.Should().BeAssignableTo<IExecute<RssSyncCommand>>();
@ -71,8 +81,8 @@ namespace NzbDrone.App.Test
[Test] [Test]
public void should_return_same_instance_via_resolve_and_resolveall() public void should_return_same_instance_via_resolve_and_resolveall()
{ {
var first = (DownloadMonitoringService)_container.Resolve<IHandle<TrackedDownloadsRemovedEvent>>(); var first = (DownloadMonitoringService)_container.GetRequiredService<IHandle<TrackedDownloadsRemovedEvent>>();
var second = _container.ResolveAll<IHandle<TrackedDownloadsRemovedEvent>>().OfType<DownloadMonitoringService>().Single(); var second = _container.GetServices<IHandle<TrackedDownloadsRemovedEvent>>().OfType<DownloadMonitoringService>().Single();
first.Should().BeSameAs(second); first.Should().BeSameAs(second);
} }
@ -80,8 +90,8 @@ namespace NzbDrone.App.Test
[Test] [Test]
public void should_return_same_instance_of_singletons_by_same_interface() public void should_return_same_instance_of_singletons_by_same_interface()
{ {
var first = _container.ResolveAll<IHandle<TrackedDownloadsRemovedEvent>>().OfType<DownloadMonitoringService>().Single(); var first = _container.GetServices<IHandle<TrackedDownloadsRemovedEvent>>().OfType<DownloadMonitoringService>().Single();
var second = _container.ResolveAll<IHandle<TrackedDownloadsRemovedEvent>>().OfType<DownloadMonitoringService>().Single(); var second = _container.GetServices<IHandle<TrackedDownloadsRemovedEvent>>().OfType<DownloadMonitoringService>().Single();
first.Should().BeSameAs(second); first.Should().BeSameAs(second);
} }
@ -89,8 +99,8 @@ namespace NzbDrone.App.Test
[Test] [Test]
public void should_return_same_instance_of_singletons_by_different_interfaces() public void should_return_same_instance_of_singletons_by_different_interfaces()
{ {
var first = _container.ResolveAll<IHandle<AlbumGrabbedEvent>>().OfType<DownloadMonitoringService>().Single(); var first = _container.GetServices<IHandle<AlbumGrabbedEvent>>().OfType<DownloadMonitoringService>().Single();
var second = (DownloadMonitoringService)_container.Resolve<IExecute<RefreshMonitoredDownloadsCommand>>(); var second = (DownloadMonitoringService)_container.GetRequiredService<IExecute<RefreshMonitoredDownloadsCommand>>();
first.Should().BeSameAs(second); first.Should().BeSameAs(second);
} }

@ -1,4 +1,3 @@
using System.ServiceProcess;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common; using NzbDrone.Common;
@ -10,7 +9,7 @@ using NzbDrone.Test.Common;
namespace NzbDrone.App.Test namespace NzbDrone.App.Test
{ {
[TestFixture] [TestFixture]
public class RouterTest : TestBase<Router> public class RouterTest : TestBase<UtilityModeRouter>
{ {
[SetUp] [SetUp]
public void Setup() public void Setup()
@ -48,33 +47,6 @@ namespace NzbDrone.App.Test
serviceProviderMock.Verify(c => c.Uninstall(ServiceProvider.SERVICE_NAME), Times.Once()); serviceProviderMock.Verify(c => c.Uninstall(ServiceProvider.SERVICE_NAME), Times.Once());
} }
[Test]
public void Route_should_call_console_service_when_application_mode_is_console()
{
Mocker.GetMock<IRuntimeInfo>().SetupGet(c => c.IsUserInteractive).Returns(true);
Subject.Route(ApplicationModes.Interactive);
Mocker.GetMock<INzbDroneConsoleFactory>().Verify(c => c.Start(), Times.Once());
}
[Test]
public void Route_should_call_service_start_when_run_in_service_mode()
{
var envMock = Mocker.GetMock<IRuntimeInfo>();
var serviceProvider = Mocker.GetMock<IServiceProvider>();
envMock.SetupGet(c => c.IsUserInteractive).Returns(false);
serviceProvider.Setup(c => c.Run(It.IsAny<ServiceBase>()));
serviceProvider.Setup(c => c.ServiceExist(It.IsAny<string>())).Returns(true);
serviceProvider.Setup(c => c.GetStatus(It.IsAny<string>())).Returns(ServiceControllerStatus.StartPending);
Subject.Route(ApplicationModes.Service);
serviceProvider.Verify(c => c.Run(It.IsAny<ServiceBase>()), Times.Once());
}
[Test] [Test]
public void show_error_on_install_if_service_already_exist() public void show_error_on_install_if_service_already_exist()
{ {

@ -2,6 +2,11 @@ using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Host.AccessControl namespace NzbDrone.Host.AccessControl
{ {
public interface IRemoteAccessAdapter
{
void MakeAccessible(bool passive);
}
public class RemoteAccessAdapter : IRemoteAccessAdapter public class RemoteAccessAdapter : IRemoteAccessAdapter
{ {
private readonly IRuntimeInfo _runtimeInfo; private readonly IRuntimeInfo _runtimeInfo;

@ -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,54 +1,90 @@
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using System.Threading; using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.WindowsServices;
using NLog; using NLog;
using NzbDrone.Common.Composition; using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Exceptions; using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation;
using NzbDrone.Common.Processes; using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Datastore.Extensions;
namespace NzbDrone.Host namespace NzbDrone.Host
{ {
public static class Bootstrap public static class Bootstrap
{ {
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(Bootstrap)); private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(Bootstrap));
private static IContainer _container;
public static void Start(StartupContext startupContext, IUserAlert userAlert, Action<IContainer> startCallback = null) public static readonly List<string> ASSEMBLIES = new List<string>
{
"Lidarr.Host",
"Lidarr.Core",
"Lidarr.SignalR",
"Lidarr.Api.V1",
"Lidarr.Http"
};
public static void Start(string[] args, Action<IHostBuilder> trayCallback = null)
{ {
try try
{ {
Logger.Info("Starting Lidarr - {0} - Version {1}", Assembly.GetCallingAssembly().Location, Assembly.GetExecutingAssembly().GetName().Version); Logger.Info("Starting Lidarr - {0} - Version {1}", Assembly.GetCallingAssembly().Location, Assembly.GetExecutingAssembly().GetName().Version);
if (!PlatformValidation.IsValidate(userAlert)) var startupContext = new StartupContext(args);
{
throw new TerminateApplicationException("Missing system requirements");
}
LongPathSupport.Enable(); LongPathSupport.Enable();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
_container = MainAppContainerBuilder.BuildContainer(startupContext);
_container.Resolve<InitializeLogger>().Initialize();
_container.Resolve<IAppFolderFactory>().Register();
_container.Resolve<IProvidePidFile>().Write();
var appMode = GetApplicationMode(startupContext); var appMode = GetApplicationMode(startupContext);
Start(appMode, startupContext); switch (appMode)
if (startCallback != null)
{
startCallback(_container);
}
else
{ {
SpinToExit(appMode); case ApplicationModes.Service:
{
Logger.Debug("Service selected");
CreateConsoleHostBuilder(args, startupContext).UseWindowsService().Build().Run();
break;
}
case ApplicationModes.Interactive:
{
Logger.Debug(trayCallback != null ? "Tray selected" : "Console selected");
var builder = CreateConsoleHostBuilder(args, startupContext);
if (trayCallback != null)
{
trayCallback(builder);
}
builder.Build().Run();
break;
}
// Utility mode
default:
{
new Container(rules => rules.WithNzbDroneRules())
.AutoAddServices(ASSEMBLIES)
.AddNzbDroneLogger()
.AddStartupContext(startupContext)
.Resolve<UtilityModeRouter>()
.Route(appMode);
break;
}
} }
} }
catch (InvalidConfigFileException ex) catch (InvalidConfigFileException ex)
@ -66,61 +102,57 @@ namespace NzbDrone.Host
} }
} }
private static void Start(ApplicationModes applicationModes, StartupContext startupContext) public static IHostBuilder CreateConsoleHostBuilder(string[] args, StartupContext context)
{ {
_container.Resolve<ReconfigureLogging>().Reconfigure(); var config = GetConfiguration(context);
if (!IsInUtilityMode(applicationModes))
{
if (startupContext.Flags.Contains(StartupContext.RESTART))
{
Thread.Sleep(2000);
}
EnsureSingleInstance(applicationModes == ApplicationModes.Service, startupContext); var bindAddress = config.GetValue(nameof(ConfigFileProvider.BindAddress), "*");
} var port = config.GetValue(nameof(ConfigFileProvider.Port), 8686);
var sslPort = config.GetValue(nameof(ConfigFileProvider.SslPort), 6868);
var enableSsl = config.GetValue(nameof(ConfigFileProvider.EnableSsl), false);
var sslCertPath = config.GetValue<string>(nameof(ConfigFileProvider.SslCertPath));
var sslCertPassword = config.GetValue<string>(nameof(ConfigFileProvider.SslCertPassword));
_container.Resolve<Router>().Route(applicationModes); var urls = new List<string> { BuildUrl("http", bindAddress, port) };
}
private static void SpinToExit(ApplicationModes applicationModes) if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
{
if (IsInUtilityMode(applicationModes))
{
return;
}
_container.Resolve<IWaitForExit>().Spin();
}
private static void EnsureSingleInstance(bool isService, IStartupContext startupContext)
{
if (startupContext.Flags.Contains(StartupContext.NO_SINGLE_INSTANCE_CHECK))
{ {
return; urls.Add(BuildUrl("https", bindAddress, sslPort));
} }
var instancePolicy = _container.Resolve<ISingleInstancePolicy>(); return new HostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
if (startupContext.Flags.Contains(StartupContext.TERMINATE)) .UseServiceProviderFactory(new DryIocServiceProviderFactory(new Container(rules => rules.WithNzbDroneRules())))
{ .ConfigureContainer<IContainer>(c =>
instancePolicy.KillAllOtherInstance(); {
} c.AutoAddServices(Bootstrap.ASSEMBLIES)
else if (startupContext.Args.ContainsKey(StartupContext.APPDATA)) .AddNzbDroneLogger()
{ .AddDatabase()
instancePolicy.WarnIfAlreadyRunning(); .AddStartupContext(context);
} })
else if (isService) .ConfigureWebHost(builder =>
{ {
instancePolicy.KillAllOtherInstance(); builder.UseUrls(urls.ToArray());
} builder.UseKestrel(options =>
else {
{ if (enableSsl && sslCertPath.IsNotNullOrWhiteSpace())
instancePolicy.PreventStartIfAlreadyRunning(); {
} options.ConfigureHttpsDefaults(configureOptions =>
{
configureOptions.ServerCertificate = ValidateSslCertificate(sslCertPath, sslCertPassword);
});
}
});
builder.ConfigureKestrel(serverOptions =>
{
serverOptions.AllowSynchronousIO = true;
serverOptions.Limits.MaxRequestBodySize = null;
});
builder.UseStartup<Startup>();
});
} }
private static ApplicationModes GetApplicationMode(IStartupContext startupContext) public static ApplicationModes GetApplicationMode(IStartupContext startupContext)
{ {
if (startupContext.Help) if (startupContext.Help)
{ {
@ -142,7 +174,7 @@ namespace NzbDrone.Host
return ApplicationModes.UninstallService; return ApplicationModes.UninstallService;
} }
if (_container.Resolve<IRuntimeInfo>().IsWindowsService) if (OsInfo.IsWindows && WindowsServiceHelpers.IsWindowsService())
{ {
return ApplicationModes.Service; return ApplicationModes.Service;
} }
@ -150,23 +182,39 @@ namespace NzbDrone.Host
return ApplicationModes.Interactive; return ApplicationModes.Interactive;
} }
private static bool IsInUtilityMode(ApplicationModes applicationMode) private static IConfiguration GetConfiguration(StartupContext context)
{
var appFolder = new AppFolderInfo(context);
return new ConfigurationBuilder()
.AddXmlFile(appFolder.GetConfigPath(), optional: true, reloadOnChange: false)
.Build();
}
private static string BuildUrl(string scheme, string bindAddress, int port)
{ {
switch (applicationMode) return $"{scheme}://{bindAddress}:{port}";
}
private static X509Certificate2 ValidateSslCertificate(string cert, string password)
{
X509Certificate2 certificate;
try
{ {
case ApplicationModes.InstallService: certificate = new X509Certificate2(cert, password, X509KeyStorageFlags.DefaultKeySet);
case ApplicationModes.UninstallService: }
case ApplicationModes.RegisterUrl: catch (CryptographicException ex)
case ApplicationModes.Help: {
{ if (ex.HResult == 0x2 || ex.HResult == 0x2006D080)
return true; {
} throw new LidarrStartupException(ex,
$"The SSL certificate file {cert} does not exist");
}
default: throw new LidarrStartupException(ex);
{
return false;
}
} }
return certificate;
} }
} }
} }

@ -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,7 +0,0 @@
namespace NzbDrone.Host
{
public interface IUserAlert
{
void Alert(string message);
}
}

@ -4,9 +4,10 @@
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Owin" Version="5.0.5" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.2" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="5.0.1" />
<PackageReference Include="DryIoc.dll" Version="4.7.4" />
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Lidarr.Api.V1\Lidarr.Api.V1.csproj" /> <ProjectReference Include="..\Lidarr.Api.V1\Lidarr.Api.V1.csproj" />

@ -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>();
}
}
}
}

@ -1,56 +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;
}
}
}
}

@ -1,60 +0,0 @@
using System.Threading;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Processes;
namespace NzbDrone.Host
{
public interface IWaitForExit
{
void Spin();
}
public class SpinService : IWaitForExit
{
private readonly IRuntimeInfo _runtimeInfo;
private readonly IProcessProvider _processProvider;
private readonly IStartupContext _startupContext;
private readonly Logger _logger;
public SpinService(IRuntimeInfo runtimeInfo, IProcessProvider processProvider, IStartupContext startupContext, Logger logger)
{
_runtimeInfo = runtimeInfo;
_processProvider = processProvider;
_startupContext = startupContext;
_logger = logger;
}
public void Spin()
{
while (!_runtimeInfo.IsExiting)
{
Thread.Sleep(1000);
}
_logger.Debug("Wait loop was terminated.");
if (_runtimeInfo.RestartPending)
{
var restartArgs = GetRestartArgs();
_logger.Info("Attempting restart with arguments: {0}", restartArgs);
_processProvider.SpawnNewProcess(_runtimeInfo.ExecutingApplication, restartArgs);
}
}
private string GetRestartArgs()
{
var args = _startupContext.PreservedArguments;
args += " /restart";
if (!args.Contains("/nobrowser"))
{
args += " /nobrowser";
}
return args;
}
}
}

@ -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();
}
}
}
}

@ -7,10 +7,13 @@ using IServiceProvider = NzbDrone.Common.IServiceProvider;
namespace NzbDrone.Host namespace NzbDrone.Host
{ {
public class Router public interface IUtilityModeRouter
{
void Route(ApplicationModes applicationModes);
}
public class UtilityModeRouter : IUtilityModeRouter
{ {
private readonly INzbDroneConsoleFactory _nzbDroneConsoleFactory;
private readonly INzbDroneServiceFactory _nzbDroneServiceFactory;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IConsoleService _consoleService; private readonly IConsoleService _consoleService;
private readonly IRuntimeInfo _runtimeInfo; private readonly IRuntimeInfo _runtimeInfo;
@ -18,17 +21,13 @@ namespace NzbDrone.Host
private readonly IRemoteAccessAdapter _remoteAccessAdapter; private readonly IRemoteAccessAdapter _remoteAccessAdapter;
private readonly Logger _logger; private readonly Logger _logger;
public Router(INzbDroneConsoleFactory nzbDroneConsoleFactory, public UtilityModeRouter(IServiceProvider serviceProvider,
INzbDroneServiceFactory nzbDroneServiceFactory,
IServiceProvider serviceProvider,
IConsoleService consoleService, IConsoleService consoleService,
IRuntimeInfo runtimeInfo, IRuntimeInfo runtimeInfo,
IProcessProvider processProvider, IProcessProvider processProvider,
IRemoteAccessAdapter remoteAccessAdapter, IRemoteAccessAdapter remoteAccessAdapter,
Logger logger) Logger logger)
{ {
_nzbDroneConsoleFactory = nzbDroneConsoleFactory;
_nzbDroneServiceFactory = nzbDroneServiceFactory;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_consoleService = consoleService; _consoleService = consoleService;
_runtimeInfo = runtimeInfo; _runtimeInfo = runtimeInfo;
@ -43,20 +42,6 @@ namespace NzbDrone.Host
switch (applicationModes) switch (applicationModes)
{ {
case ApplicationModes.Service:
{
_logger.Debug("Service selected");
_serviceProvider.Run(_nzbDroneServiceFactory.Build());
break;
}
case ApplicationModes.Interactive:
{
_logger.Debug(_runtimeInfo.IsWindowsTray ? "Tray selected" : "Console selected");
_nzbDroneConsoleFactory.Start();
break;
}
case ApplicationModes.InstallService: case ApplicationModes.InstallService:
{ {
_logger.Debug("Install Service selected"); _logger.Debug("Install Service selected");

@ -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}";
}
}
}

@ -164,7 +164,7 @@ namespace NzbDrone.Test.Common.AutoMoq
RegisterPlatformLibrary(container); RegisterPlatformLibrary(container);
AddTheAutoMockingContainerExtensionToTheContainer(container); AddTheAutoMockingContainerExtensionToTheContainer(container);
ContainerBuilderBase.RegisterSQLiteResolver(); AssemblyLoader.RegisterSQLiteResolver();
} }
private static void AddTheAutoMockingContainerExtensionToTheContainer(IUnityContainer container) private static void AddTheAutoMockingContainerExtensionToTheContainer(IUnityContainer container)

@ -4,6 +4,8 @@
<TargetFrameworks>net5.0</TargetFrameworks> <TargetFrameworks>net5.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DryIoc.dll" Version="4.7.4" />
<PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
<PackageReference Include="NLog" Version="4.7.9" /> <PackageReference Include="NLog" Version="4.7.9" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using DryIoc;
using NLog; using NLog;
using NzbDrone.Common.Composition; using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Common.Processes; using NzbDrone.Common.Processes;
using NzbDrone.Update.UpdateEngine; using NzbDrone.Update.UpdateEngine;
@ -18,8 +21,6 @@ namespace NzbDrone.Update
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(UpdateApp)); private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(UpdateApp));
private static IContainer _container;
public UpdateApp(IInstallUpdateService installUpdateService, IProcessProvider processProvider) public UpdateApp(IInstallUpdateService installUpdateService, IProcessProvider processProvider)
{ {
_installUpdateService = installUpdateService; _installUpdateService = installUpdateService;
@ -35,9 +36,13 @@ namespace NzbDrone.Update
Logger.Info("Starting Lidarr Update Client"); Logger.Info("Starting Lidarr Update Client");
_container = UpdateContainerBuilder.Build(startupContext); var container = new Container(rules => rules.WithNzbDroneRules())
_container.Resolve<InitializeLogger>().Initialize(); .AutoAddServices(new List<string> { "Lidarr.Update" })
_container.Resolve<UpdateApp>().Start(args); .AddNzbDroneLogger()
.AddStartupContext(startupContext);
container.Resolve<InitializeLogger>().Initialize();
container.Resolve<UpdateApp>().Start(args);
Logger.Info("Update completed successfully"); Logger.Info("Update completed successfully");
} }

@ -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;
}
}
}

@ -13,6 +13,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Host\Lidarr.Host.csproj" /> <ProjectReference Include="..\NzbDrone.Host\Lidarr.Host.csproj" />
<ProjectReference Include="..\NzbDrone.Windows\Lidarr.Windows.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Properties\Resources.Designer.cs"> <Compile Update="Properties\Resources.Designer.cs">

@ -1,13 +0,0 @@
using System.Windows.Forms;
using NzbDrone.Host;
namespace NzbDrone
{
public class MessageBoxUserAlert : IUserAlert
{
public void Alert(string message)
{
MessageBox.Show(text: message, buttons: MessageBoxButtons.OK, icon: MessageBoxIcon.Warning, caption: "NzbDrone");
}
}
}

@ -1,6 +1,9 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using Microsoft.Extensions.Hosting;
using NLog; using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Processes; using NzbDrone.Common.Processes;
@ -8,12 +11,7 @@ using NzbDrone.Host;
namespace NzbDrone.SysTray namespace NzbDrone.SysTray
{ {
public interface ISystemTrayApp public class SystemTrayApp : Form, IHostedService
{
void Start();
}
public class SystemTrayApp : Form, ISystemTrayApp
{ {
private readonly IBrowserService _browserService; private readonly IBrowserService _browserService;
private readonly IRuntimeInfo _runtimeInfo; private readonly IRuntimeInfo _runtimeInfo;
@ -34,8 +32,12 @@ namespace NzbDrone.SysTray
Application.ThreadException += OnThreadException; Application.ThreadException += OnThreadException;
Application.ApplicationExit += OnApplicationExit; Application.ApplicationExit += OnApplicationExit;
Application.SetHighDpiMode(HighDpiMode.PerMonitor);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
_trayMenu.Items.Add(new ToolStripMenuItem("Launch Browser", null, LaunchBrowser)); _trayMenu.Items.Add(new ToolStripMenuItem("Launch Browser", null, LaunchBrowser));
_trayMenu.Items.Add(new ToolStripMenuItem("-")); _trayMenu.Items.Add(new ToolStripSeparator());
_trayMenu.Items.Add(new ToolStripMenuItem("Exit", null, OnExit)); _trayMenu.Items.Add(new ToolStripMenuItem("Exit", null, OnExit));
_trayIcon.Text = string.Format("Lidarr - {0}", BuildInfo.Version); _trayIcon.Text = string.Format("Lidarr - {0}", BuildInfo.Version);
@ -48,6 +50,20 @@ namespace NzbDrone.SysTray
Application.Run(this); Application.Run(this);
} }
public Task StartAsync(CancellationToken cancellationToken)
{
var thread = new Thread(Start);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
protected override void OnClosing(CancelEventArgs e) protected override void OnClosing(CancelEventArgs e)
{ {
DisposeTrayIcon(); DisposeTrayIcon();

@ -1,5 +1,7 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NLog; using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation;
@ -20,11 +22,9 @@ namespace NzbDrone
NzbDroneLogger.Register(startupArgs, false, true); NzbDroneLogger.Register(startupArgs, false, true);
Bootstrap.Start(startupArgs, new MessageBoxUserAlert(), container => Bootstrap.Start(args, e =>
{ {
container.Register<ISystemTrayApp, SystemTrayApp>(); e.ConfigureServices((_, s) => s.AddSingleton<IHostedService, SystemTrayApp>());
var trayApp = container.Resolve<ISystemTrayApp>();
trayApp.Start();
}); });
} }
catch (Exception e) catch (Exception e)

Loading…
Cancel
Save