Handling configuration

This commit is contained in:
Zoe Roux 2021-07-18 17:06:29 +02:00
parent 84458d3413
commit 6ebf8a8361
11 changed files with 174 additions and 75 deletions

View File

@ -9,6 +9,7 @@ using IdentityServer4.Services;
using Kyoo.Authentication.Models;
using Kyoo.Authentication.Views;
using Kyoo.Controllers;
using Kyoo.Models.Attributes;
using Kyoo.Models.Permissions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@ -64,6 +65,11 @@ namespace Kyoo.Authentication
/// The environment information to check if the app runs in debug mode
/// </summary>
private readonly IWebHostEnvironment _environment;
/// <summary>
/// The configuration manager used to register typed/untyped implementations.
/// </summary>
[Injected] public IConfigurationManager ConfigurationManager { private get; set; }
/// <summary>
@ -98,9 +104,7 @@ namespace Kyoo.Authentication
services.Configure<PermissionOption>(_configuration.GetSection(PermissionOption.Path));
services.Configure<CertificateOption>(_configuration.GetSection(CertificateOption.Path));
services.Configure<AuthenticationOption>(_configuration.GetSection(AuthenticationOption.Path));
services.AddConfiguration<AuthenticationOption>(AuthenticationOption.Path);
List<Client> clients = new();
_configuration.GetSection("authentication:clients").Bind(clients);
CertificateOption certificateOptions = new();
@ -139,6 +143,8 @@ namespace Kyoo.Authentication
/// <inheritdoc />
public void ConfigureAspNet(IApplicationBuilder app)
{
ConfigurationManager.AddTyped<AuthenticationOption>(AuthenticationOption.Path);
app.UseCookiePolicy(new CookiePolicyOptions
{
MinimumSameSitePolicy = SameSiteMode.Strict

View File

@ -12,6 +12,21 @@ namespace Kyoo.Controllers
/// </summary>
public interface IConfigurationManager
{
/// <summary>
/// Add an editable configuration to the editable configuration list
/// </summary>
/// <param name="path">The root path of the editable configuration. It should not be a nested type.</param>
/// <typeparam name="T">The type of the configuration</typeparam>
void AddTyped<T>(string path);
/// <summary>
/// Add an editable configuration to the editable configuration list.
/// WARNING: this method allow you to add an unmanaged type. This type won't be editable. This can be used
/// for external libraries or variable arguments.
/// </summary>
/// <param name="path">The root path of the editable configuration. It should not be a nested type.</param>
void AddUntyped(string path);
/// <summary>
/// Get the value of a setting using it's path.
/// </summary>

View File

@ -1,10 +1,7 @@
using System.Linq;
using Autofac;
using Autofac.Builder;
using Kyoo.Controllers;
using Kyoo.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Kyoo
{
@ -75,37 +72,6 @@ namespace Kyoo
return builder.RegisterRepository<T2>().As<T>();
}
/// <summary>
/// Add an editable configuration to the editable configuration list
/// </summary>
/// <param name="services">The service collection to edit</param>
/// <param name="path">The root path of the editable configuration. It should not be a nested type.</param>
/// <typeparam name="T">The type of the configuration</typeparam>
/// <returns>The given service collection is returned.</returns>
public static IServiceCollection AddConfiguration<T>(this IServiceCollection services, string path)
where T : class
{
if (services.Any(x => x.ServiceType == typeof(T)))
return services;
foreach (ConfigurationReference confRef in ConfigurationReference.CreateReference<T>(path))
services.AddSingleton(confRef);
return services;
}
/// <summary>
/// Add an editable configuration to the editable configuration list.
/// WARNING: this method allow you to add an unmanaged type. This type won't be editable. This can be used
/// for external libraries or variable arguments.
/// </summary>
/// <param name="services">The service collection to edit</param>
/// <param name="path">The root path of the editable configuration. It should not be a nested type.</param>
/// <returns>The given service collection is returned.</returns>
public static IServiceCollection AddUntypedConfiguration(this IServiceCollection services, string path)
{
services.AddSingleton(ConfigurationReference.CreateUntyped(path));
return services;
}
/// <summary>
/// Get the public URL of kyoo using the given configuration instance.
/// </summary>

View File

@ -19,8 +19,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="5.0.0" />
<PackageReference Include="TvDbSharper" Version="3.2.2" />
</ItemGroup>

View File

@ -1,7 +1,12 @@
using System;
using System.Collections.Generic;
using Autofac;
using Kyoo.Authentication.Models;
using Kyoo.Controllers;
using Kyoo.Models.Attributes;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TvDbSharper;
namespace Kyoo.TheTvdb
@ -33,11 +38,44 @@ namespace Kyoo.TheTvdb
public ICollection<Type> Requires => ArraySegment<Type>.Empty;
/// <summary>
/// The configuration to use.
/// </summary>
private readonly IConfiguration _configuration;
/// <summary>
/// The configuration manager used to register typed/untyped implementations.
/// </summary>
[Injected] public IConfigurationManager ConfigurationManager { private get; set; }
/// <summary>
/// Create a new tvdb module instance and use the given configuration.
/// </summary>
/// <param name="configuration">The configuration to use</param>
public PluginTvdb(IConfiguration configuration)
{
_configuration = configuration;
}
/// <inheritdoc />
public void Configure(ContainerBuilder builder)
{
builder.RegisterType<TvDbClient>().As<ITvDbClient>();
builder.RegisterProvider<ProviderTvdb>();
}
/// <inheritdoc />
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
{
services.Configure<TvdbOption>(_configuration.GetSection(TvdbOption.Path));
}
/// <inheritdoc />
public void ConfigureAspNet(IApplicationBuilder app)
{
ConfigurationManager.AddTyped<TvdbOption>(TvdbOption.Path);
}
}
}

View File

@ -36,7 +36,29 @@ namespace Kyoo.Controllers
_references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase);
}
private Type GetType(string path)
/// <inheritdoc />
public void AddTyped<T>(string path)
{
foreach (ConfigurationReference confRef in ConfigurationReference.CreateReference<T>(path))
_references.Add(confRef.Path, confRef.Type);
}
/// <inheritdoc />
public void AddUntyped(string path)
{
ConfigurationReference config = ConfigurationReference.CreateUntyped(path);
_references.Add(config.Path, config.Type);
}
/// <summary>
/// Get the type of the resource at the given path
/// </summary>
/// <param name="path">The path of the resource</param>
/// <exception cref="ArgumentException">The path is not editable or readable</exception>
/// <exception cref="ItemNotFoundException">No configuration exists for the given path</exception>
/// <returns>The type of the resource at the given path</returns>
private Type _GetType(string path)
{
path = path.Replace("__", ":");
@ -59,7 +81,7 @@ namespace Kyoo.Controllers
{
path = path.Replace("__", ":");
// TODO handle lists and dictionaries.
Type type = GetType(path);
Type type = _GetType(path);
object ret = _configuration.GetValue(type, path);
if (ret != null)
return ret;
@ -73,7 +95,7 @@ namespace Kyoo.Controllers
{
path = path.Replace("__", ":");
// TODO handle lists and dictionaries.
Type type = GetType(path);
Type type = _GetType(path);
if (typeof(T).IsAssignableFrom(type))
throw new InvalidCastException($"The type {typeof(T).Name} is not valid for " +
$"a resource of type {type.Name}.");
@ -84,12 +106,12 @@ namespace Kyoo.Controllers
public async Task EditValue(string path, object value)
{
path = path.Replace("__", ":");
Type type = GetType(path);
Type type = _GetType(path);
value = JObject.FromObject(value).ToObject(type);
if (value == null)
throw new ArgumentException("Invalid value format.");
ExpandoObject config = ToObject(_configuration);
ExpandoObject config = _ToObject(_configuration);
IDictionary<string, object> configDic = config;
configDic[path] = value;
JObject obj = JObject.FromObject(config);
@ -104,7 +126,7 @@ namespace Kyoo.Controllers
/// <param name="config">The configuration to transform</param>
/// <returns>A strongly typed representation of the configuration.</returns>
[SuppressMessage("ReSharper", "RedundantJumpStatement")]
private ExpandoObject ToObject(IConfiguration config)
private ExpandoObject _ToObject(IConfiguration config)
{
ExpandoObject obj = new();
@ -112,12 +134,12 @@ namespace Kyoo.Controllers
{
try
{
Type type = GetType(section.Path);
Type type = _GetType(section.Path);
obj.TryAdd(section.Key, section.Get(type));
}
catch (ArgumentException)
{
obj.TryAdd(section.Key, ToUntyped(section));
obj.TryAdd(section.Key, _ToUntyped(section));
}
catch
{
@ -133,13 +155,13 @@ namespace Kyoo.Controllers
/// </summary>
/// <param name="config">The section to convert</param>
/// <returns>The converted section</returns>
private static object ToUntyped(IConfigurationSection config)
private static object _ToUntyped(IConfigurationSection config)
{
ExpandoObject obj = new();
foreach (IConfigurationSection section in config.GetChildren())
{
obj.TryAdd(section.Key, ToUntyped(section));
obj.TryAdd(section.Key, _ToUntyped(section));
}
if (!obj.Any())

View File

@ -22,7 +22,7 @@ namespace Kyoo.Controllers
/// <summary>
/// The service provider. It allow plugin's activation.
/// </summary>
private readonly IServiceProvider _provider;
private IServiceProvider _provider;
/// <summary>
/// The configuration to get the plugin's directory.
/// </summary>
@ -52,6 +52,13 @@ namespace Kyoo.Controllers
_logger = logger;
}
public void SetProvider(IServiceProvider provider)
{
// TODO temporary bullshit to inject services before the configure asp net.
// TODO should rework this when the host will be reworked, as well as the asp net configure.
_provider = provider;
}
/// <inheritdoc />
public T GetPlugin<T>(string name)
@ -128,6 +135,7 @@ namespace Kyoo.Controllers
_logger.LogInformation("Plugin enabled: {Plugins}", _plugins.Select(x => x.Name));
}
/// <inheritdoc />
public void ConfigureContainer(ContainerBuilder builder)
{
foreach (IPlugin plugin in _plugins)
@ -146,7 +154,11 @@ namespace Kyoo.Controllers
public void ConfigureAspnet(IApplicationBuilder app)
{
foreach (IPlugin plugin in _plugins)
{
using IServiceScope scope = _provider.CreateScope();
Helper.InjectServices(plugin, x => scope.ServiceProvider.GetRequiredService(x));
plugin.ConfigureAspNet(app);
}
}
/// <summary>

View File

@ -165,27 +165,12 @@ namespace Kyoo.Controllers
}));
using IServiceScope scope = _provider.CreateScope();
InjectServices(task, x => scope.ServiceProvider.GetRequiredService(x));
Helper.InjectServices(task, x => scope.ServiceProvider.GetRequiredService(x));
await task.Run(args, progress, _taskToken.Token);
InjectServices(task, _ => null);
Helper.InjectServices(task, _ => null);
_logger.LogInformation("Task finished: {Task}", task.Name);
}
/// <summary>
/// Inject services into the <see cref="InjectedAttribute"/> marked properties of the given object.
/// </summary>
/// <param name="obj">The object to inject</param>
/// <param name="retrieve">The function used to retrieve services. (The function is called immediately)</param>
private static void InjectServices(ITask obj, [InstantHandle] Func<Type, object> retrieve)
{
IEnumerable<PropertyInfo> properties = obj.GetType().GetProperties()
.Where(x => x.GetCustomAttribute<InjectedAttribute>() != null)
.Where(x => x.CanWrite);
foreach (PropertyInfo property in properties)
property.SetValue(obj, retrieve(property.PropertyType));
}
/// <summary>
/// Start tasks that are scheduled for start.
/// </summary>

View File

@ -5,6 +5,7 @@ using Autofac;
using Autofac.Core;
using Autofac.Core.Registration;
using Kyoo.Controllers;
using Kyoo.Models.Attributes;
using Kyoo.Models.Options;
using Kyoo.Models.Permissions;
using Kyoo.Tasks;
@ -81,6 +82,11 @@ namespace Kyoo
/// The configuration to use.
/// </summary>
private readonly IConfiguration _configuration;
/// <summary>
/// The configuration manager used to register typed/untyped implementations.
/// </summary>
[Injected] public IConfigurationManager ConfigurationManager { private get; set; }
/// <summary>
@ -136,14 +142,9 @@ namespace Kyoo
string publicUrl = _configuration.GetPublicUrl();
services.Configure<BasicOptions>(_configuration.GetSection(BasicOptions.Path));
services.AddConfiguration<BasicOptions>(BasicOptions.Path);
services.Configure<TaskOptions>(_configuration.GetSection(TaskOptions.Path));
services.AddConfiguration<TaskOptions>(TaskOptions.Path);
services.Configure<MediaOptions>(_configuration.GetSection(MediaOptions.Path));
services.AddConfiguration<MediaOptions>(MediaOptions.Path);
services.AddUntypedConfiguration("database");
services.AddUntypedConfiguration("logging");
services.AddControllers()
.AddNewtonsoftJson(x =>
{
@ -157,6 +158,12 @@ namespace Kyoo
/// <inheritdoc />
public void ConfigureAspNet(IApplicationBuilder app)
{
ConfigurationManager.AddTyped<BasicOptions>(BasicOptions.Path);
ConfigurationManager.AddTyped<TaskOptions>(TaskOptions.Path);
ConfigurationManager.AddTyped<MediaOptions>(MediaOptions.Path);
ConfigurationManager.AddUntyped("database");
ConfigurationManager.AddUntyped("logging");
FileExtensionContentTypeProvider contentTypeProvider = new();
contentTypeProvider.Mappings[".data"] = "application/octet-stream";
app.UseStaticFiles(new StaticFileOptions

47
Kyoo/Helper.cs Normal file
View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Kyoo.Models.Attributes;
using Newtonsoft.Json;
namespace Kyoo
{
public static class Helper
{
/// <summary>
/// Inject services into the <see cref="InjectedAttribute"/> marked properties of the given object.
/// </summary>
/// <param name="obj">The object to inject</param>
/// <param name="retrieve">The function used to retrieve services. (The function is called immediately)</param>
public static void InjectServices(object obj, [InstantHandle] Func<Type, object> retrieve)
{
IEnumerable<PropertyInfo> properties = obj.GetType().GetProperties()
.Where(x => x.GetCustomAttribute<InjectedAttribute>() != null)
.Where(x => x.CanWrite);
foreach (PropertyInfo property in properties)
property.SetValue(obj, retrieve(property.PropertyType));
}
/// <summary>
/// An helper method to get json content from an http server. This is a temporary thing and will probably be
/// replaced by a call to the function of the same name in the <c>System.Net.Http.Json</c> namespace when .net6
/// gets released.
/// </summary>
/// <param name="client">The http server to use.</param>
/// <param name="url">The url to retrieve</param>
/// <typeparam name="T">The type of object to convert</typeparam>
/// <returns>A T representing the json contained at the given url.</returns>
public static async Task<T> GetFromJsonAsync<T>(this HttpClient client, string url)
{
HttpResponseMessage ret = await client.GetAsync(url);
ret.EnsureSuccessStatusCode();
string content = await ret.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(content);
}
}
}

View File

@ -71,7 +71,6 @@ namespace Kyoo
services.AddHttpClient();
// services.AddTransient(typeof(Lazy<>), typeof(LazyDi<>));
_plugins.ConfigureServices(services);
}
@ -87,7 +86,7 @@ namespace Kyoo
/// </summary>
/// <param name="app">The asp net host to configure</param>
/// <param name="env">The host environment (is the app in development mode?)</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider provider)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
@ -114,7 +113,9 @@ namespace Kyoo
return next();
});
app.UseResponseCompression();
if (_plugins is PluginManager manager)
manager.SetProvider(provider);
_plugins.ConfigureAspnet(app);
app.UseSpa(spa =>