Host: Using a genric host instead of a web host

This commit is contained in:
Zoe Roux 2021-08-06 23:14:20 +02:00
parent 33b74cac37
commit a64cbcea62
7 changed files with 149 additions and 57 deletions

View File

@ -2,25 +2,11 @@ using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
namespace Kyoo.Controllers
{
/// <summary>
/// A class wrapping a value that will be set after the completion of the task it is related to.
/// </summary>
/// <remarks>
/// This class replace the use of an out parameter on a task since tasks and out can't be combined.
/// </remarks>
/// <typeparam name="T">The type of the value</typeparam>
public class AsyncRef<T>
{
/// <summary>
/// The value that will be set before the completion of the task.
/// </summary>
public T Value { get; set; }
}
/// <summary>
/// A service to abstract the file system to allow custom file systems (like distant file systems or external providers)
/// </summary>

View File

@ -55,7 +55,7 @@ namespace Kyoo.Controllers
/// <returns>A new task parameter.</returns>
public static TaskParameter Create<T>(string name, string description)
{
return new()
return new TaskParameter
{
Name = name,
Description = description,
@ -72,7 +72,7 @@ namespace Kyoo.Controllers
/// <returns>A new task parameter.</returns>
public static TaskParameter CreateRequired<T>(string name, string description)
{
return new()
return new TaskParameter
{
Name = name,
Description = description,

View File

@ -0,0 +1,17 @@
namespace Kyoo.Models
{
/// <summary>
/// A class wrapping a value that will be set after the completion of the task it is related to.
/// </summary>
/// <remarks>
/// This class replace the use of an out parameter on a task since tasks and out can't be combined.
/// </remarks>
/// <typeparam name="T">The type of the value</typeparam>
public class AsyncRef<T>
{
/// <summary>
/// The value that will be set before the completion of the task.
/// </summary>
public T Value { get; set; }
}
}

View File

@ -5,6 +5,7 @@ using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Kyoo.Common.Models.Attributes;
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
namespace Kyoo.Controllers

View File

@ -26,7 +26,7 @@ namespace Kyoo.Controllers
/// <summary>
/// The configuration to get the plugin's directory.
/// </summary>
private readonly IOptionsMonitor<BasicOptions> _options;
private readonly IOptions<BasicOptions> _options;
/// <summary>
/// The logger used by this class.
/// </summary>
@ -44,7 +44,7 @@ namespace Kyoo.Controllers
/// <param name="options">The configuration instance, to get the plugin's directory path.</param>
/// <param name="logger">The logger used by this class.</param>
public PluginManager(IServiceProvider provider,
IOptionsMonitor<BasicOptions> options,
IOptions<BasicOptions> options,
ILogger<PluginManager> logger)
{
_provider = provider;
@ -106,7 +106,7 @@ namespace Kyoo.Controllers
/// <inheritdoc />
public void LoadPlugins(ICollection<IPlugin> plugins)
{
string pluginFolder = _options.CurrentValue.PluginPath;
string pluginFolder = _options.Value.PluginPath;
if (!Directory.Exists(pluginFolder))
Directory.CreateDirectory(pluginFolder);

View File

@ -1,13 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Kyoo
@ -32,7 +32,7 @@ namespace Kyoo
if (!File.Exists("./settings.json"))
File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "settings.json"), "settings.json");
IWebHostBuilder builder = CreateWebHostBuilder(args);
IHostBuilder builder = CreateWebHostBuilder(args);
bool? debug = Environment.GetEnvironmentVariable("ENVIRONMENT")?.ToLowerInvariant() switch
{
@ -84,24 +84,11 @@ namespace Kyoo
}
/// <summary>
/// Create a a web host
/// Configure the logging.
/// </summary>
/// <param name="args">Command line parameters that can be handled by kestrel</param>
/// <returns>A new web host instance</returns>
private static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
IConfiguration configuration = SetupConfig(new ConfigurationBuilder(), args).Build();
return new WebHostBuilder()
.ConfigureServices(x =>
{
AutofacServiceProviderFactory factory = new();
x.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<ContainerBuilder>>(factory));
})
.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
.UseConfiguration(configuration)
.ConfigureAppConfiguration(x => SetupConfig(x, args))
.ConfigureLogging((context, builder) =>
/// <param name="context">The host context that contains the configuration</param>
/// <param name="builder">The logger builder to configure.</param>
private static void _ConfigureLogging(HostBuilderContext context, ILoggingBuilder builder)
{
builder.AddConfiguration(context.Configuration.GetSection("logging"))
.AddSimpleConsole(x =>
@ -110,13 +97,53 @@ namespace Kyoo
})
.AddDebug()
.AddEventSourceLogger();
})
}
/// <summary>
/// Create a a web host
/// </summary>
/// <param name="args">Command line parameters that can be handled by kestrel</param>
/// <param name="loggingConfiguration">
/// An action to configure the logging. If it is null, <see cref="_ConfigureLogging"/> will be used.
/// </param>
/// <returns>A new web host instance</returns>
public static IHostBuilder CreateWebHostBuilder(string[] args,
Action<HostBuilderContext, ILoggingBuilder> loggingConfiguration = null)
{
IConfiguration configuration = SetupConfig(new ConfigurationBuilder(), args).Build();
loggingConfiguration ??= _ConfigureLogging;
return new HostBuilder()
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
.ConfigureAppConfiguration(x => SetupConfig(x, args))
.ConfigureLogging(loggingConfiguration)
.ConfigureServices(x => x.AddRouting())
.ConfigureWebHost(x => x
.UseKestrel(options => { options.AddServerHeader = false; })
.UseIIS()
.UseIISIntegration()
.UseUrls(configuration.GetValue<string>("basics:url"))
.UseStartup<Startup>();
.UseStartup(host => new Startup(
host.HostingEnvironment,
host.Configuration,
LoggerFactory.Create(builder => loggingConfiguration(host.ToGenericHost(), builder)))
)
);
}
/// <summary>
/// Convert an <see cref="WebHostBuilderContext"/> to a <see cref="HostBuilderContext"/>.
/// </summary>
/// <param name="host">The <see cref="WebHostBuilderContext"/> to convert.</param>
/// <returns>A <see cref="HostBuilderContext"/> containing the same properties.</returns>
private static HostBuilderContext ToGenericHost(this WebHostBuilderContext host)
{
return new HostBuilderContext(new Dictionary<object, object>())
{
Configuration = host.Configuration,
HostingEnvironment = host.HostingEnvironment
};
}
}
}

View File

@ -34,31 +34,36 @@ namespace Kyoo
/// <summary>
/// Created from the DI container, those services are needed to load information and instantiate plugins.s
/// </summary>
/// <param name="hostProvider">
/// The ServiceProvider used to create this <see cref="Startup"/> instance.
/// The host provider that contains only well-known services that are Kyoo independent.
/// This is used to instantiate plugins that might need a logger, a configuration or an host environment.
/// <param name="hostEnvironment">
/// The host environment that could be used by plugins to configure themself.
/// </param>
/// <param name="configuration">The configuration context</param>
/// <param name="loggerFactory">A logger factory used to create a logger for the plugin manager.</param>
public Startup(IServiceProvider hostProvider, IConfiguration configuration, ILoggerFactory loggerFactory, IWebHostEnvironment host)
public Startup(IWebHostEnvironment hostEnvironment,
IConfiguration configuration,
ILoggerFactory loggerFactory)
{
IOptionsMonitor<BasicOptions> options = hostProvider.GetService<IOptionsMonitor<BasicOptions>>();
_plugins = new PluginManager(hostProvider, options, loggerFactory.CreateLogger<PluginManager>());
HostServiceProvider hostProvider = new(hostEnvironment, configuration, loggerFactory);
_plugins = new PluginManager(
hostProvider,
Options.Create(configuration.GetSection(BasicOptions.Path).Get<BasicOptions>()),
loggerFactory.CreateLogger<PluginManager>()
);
// TODO remove postgres from here and load it like a normal plugin.
// TODO maybe keep all core-plugins here to simplify the build process but use their typeof in the method
// (to allow simple constructor changes), leaving the instantiation responsibility to the plugin manager.
_plugins.LoadPlugins(new IPlugin[] {
new CoreModule(configuration),
new PostgresModule(configuration, host),
new PostgresModule(configuration, hostEnvironment),
// new SqLiteModule(configuration, host),
new AuthenticationModule(configuration, loggerFactory, host),
new AuthenticationModule(configuration, loggerFactory, hostEnvironment),
new PluginTvdb(configuration),
new PluginTmdb(configuration)
});
}
/// <summary>
/// Configure the WebApp services context.
/// Configure the services context via the <see cref="PluginManager"/>.
/// </summary>
/// <param name="services">The service collection to fill.</param>
public void ConfigureServices(IServiceCollection services)
@ -79,6 +84,10 @@ namespace Kyoo
_plugins.ConfigureServices(services);
}
/// <summary>
/// Configure the autofac container via the <see cref="PluginManager"/>.
/// </summary>
/// <param name="builder">The builder to configure.</param>
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule<AttributedMetadataModule>();
@ -132,5 +141,57 @@ namespace Kyoo
spa.UseAngularCliServer("start");
});
}
/// <summary>
/// A simple host service provider used to activate plugins instance.
/// The same services as a generic host are available and an <see cref="ILoggerFactory"/> has been added.
/// </summary>
private class HostServiceProvider : IServiceProvider
{
/// <summary>
/// The host environment that could be used by plugins to configure themself.
/// </summary>
private readonly IWebHostEnvironment _hostEnvironment;
/// <summary>
/// The configuration context.
/// </summary>
private readonly IConfiguration _configuration;
/// <summary>
/// A logger factory used to create a logger for the plugin manager.
/// </summary>
private readonly ILoggerFactory _loggerFactory;
/// <summary>
/// Create a new <see cref="HostServiceProvider"/> that will return given services when asked.
/// </summary>
/// <param name="hostEnvironment">
/// The host environment that could be used by plugins to configure themself.
/// </param>
/// <param name="configuration">The configuration context</param>
/// <param name="loggerFactory">A logger factory used to create a logger for the plugin manager.</param>
public HostServiceProvider(IWebHostEnvironment hostEnvironment,
IConfiguration configuration,
ILoggerFactory loggerFactory)
{
_hostEnvironment = hostEnvironment;
_configuration = configuration;
_loggerFactory = loggerFactory;
}
/// <inheritdoc />
public object GetService(Type serviceType)
{
if (serviceType == typeof(IWebHostEnvironment) || serviceType == typeof(IHostEnvironment))
return _hostEnvironment;
if (serviceType == typeof(IConfiguration))
return _configuration;
if (serviceType == typeof(ILoggerFactory))
return _loggerFactory;
return null;
}
}
}
}