diff --git a/Kyoo.Authentication/AuthenticationModule.cs b/Kyoo.Authentication/AuthenticationModule.cs index 8e2c78c4..1e9dcd01 100644 --- a/Kyoo.Authentication/AuthenticationModule.cs +++ b/Kyoo.Authentication/AuthenticationModule.cs @@ -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 /// private readonly IWebHostEnvironment _environment; + + /// + /// The configuration manager used to register typed/untyped implementations. + /// + [Injected] public IConfigurationManager ConfigurationManager { private get; set; } /// @@ -98,9 +104,7 @@ namespace Kyoo.Authentication services.Configure(_configuration.GetSection(PermissionOption.Path)); services.Configure(_configuration.GetSection(CertificateOption.Path)); services.Configure(_configuration.GetSection(AuthenticationOption.Path)); - services.AddConfiguration(AuthenticationOption.Path); - - + List clients = new(); _configuration.GetSection("authentication:clients").Bind(clients); CertificateOption certificateOptions = new(); @@ -139,6 +143,8 @@ namespace Kyoo.Authentication /// public void ConfigureAspNet(IApplicationBuilder app) { + ConfigurationManager.AddTyped(AuthenticationOption.Path); + app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Strict diff --git a/Kyoo.Common/Controllers/IConfigurationManager.cs b/Kyoo.Common/Controllers/IConfigurationManager.cs index 9159d92c..02430b10 100644 --- a/Kyoo.Common/Controllers/IConfigurationManager.cs +++ b/Kyoo.Common/Controllers/IConfigurationManager.cs @@ -12,6 +12,21 @@ namespace Kyoo.Controllers /// public interface IConfigurationManager { + /// + /// Add an editable configuration to the editable configuration list + /// + /// The root path of the editable configuration. It should not be a nested type. + /// The type of the configuration + void AddTyped(string path); + + /// + /// 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. + /// + /// The root path of the editable configuration. It should not be a nested type. + void AddUntyped(string path); + /// /// Get the value of a setting using it's path. /// diff --git a/Kyoo.Common/Module.cs b/Kyoo.Common/Module.cs index d4442d98..0e8de063 100644 --- a/Kyoo.Common/Module.cs +++ b/Kyoo.Common/Module.cs @@ -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().As(); } - /// - /// Add an editable configuration to the editable configuration list - /// - /// The service collection to edit - /// The root path of the editable configuration. It should not be a nested type. - /// The type of the configuration - /// The given service collection is returned. - public static IServiceCollection AddConfiguration(this IServiceCollection services, string path) - where T : class - { - if (services.Any(x => x.ServiceType == typeof(T))) - return services; - foreach (ConfigurationReference confRef in ConfigurationReference.CreateReference(path)) - services.AddSingleton(confRef); - return services; - } - - /// - /// 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. - /// - /// The service collection to edit - /// The root path of the editable configuration. It should not be a nested type. - /// The given service collection is returned. - public static IServiceCollection AddUntypedConfiguration(this IServiceCollection services, string path) - { - services.AddSingleton(ConfigurationReference.CreateUntyped(path)); - return services; - } - /// /// Get the public URL of kyoo using the given configuration instance. /// diff --git a/Kyoo.TheTvdb/Kyoo.TheTvdb.csproj b/Kyoo.TheTvdb/Kyoo.TheTvdb.csproj index 8877cb95..ac7dad56 100644 --- a/Kyoo.TheTvdb/Kyoo.TheTvdb.csproj +++ b/Kyoo.TheTvdb/Kyoo.TheTvdb.csproj @@ -19,8 +19,8 @@ - + diff --git a/Kyoo.TheTvdb/PluginTvdb.cs b/Kyoo.TheTvdb/PluginTvdb.cs index 62fc0885..1d008fbf 100644 --- a/Kyoo.TheTvdb/PluginTvdb.cs +++ b/Kyoo.TheTvdb/PluginTvdb.cs @@ -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 Requires => ArraySegment.Empty; + /// + /// The configuration to use. + /// + private readonly IConfiguration _configuration; + + /// + /// The configuration manager used to register typed/untyped implementations. + /// + [Injected] public IConfigurationManager ConfigurationManager { private get; set; } + + + /// + /// Create a new tvdb module instance and use the given configuration. + /// + /// The configuration to use + public PluginTvdb(IConfiguration configuration) + { + _configuration = configuration; + } + + /// public void Configure(ContainerBuilder builder) { builder.RegisterType().As(); builder.RegisterProvider(); } + + /// + public void Configure(IServiceCollection services, ICollection availableTypes) + { + services.Configure(_configuration.GetSection(TvdbOption.Path)); + } + + /// + public void ConfigureAspNet(IApplicationBuilder app) + { + ConfigurationManager.AddTyped(TvdbOption.Path); + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/ConfigurationManager.cs b/Kyoo/Controllers/ConfigurationManager.cs index c0a63895..32b2e7f7 100644 --- a/Kyoo/Controllers/ConfigurationManager.cs +++ b/Kyoo/Controllers/ConfigurationManager.cs @@ -36,7 +36,29 @@ namespace Kyoo.Controllers _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); } - private Type GetType(string path) + + /// + public void AddTyped(string path) + { + foreach (ConfigurationReference confRef in ConfigurationReference.CreateReference(path)) + _references.Add(confRef.Path, confRef.Type); + } + + /// + public void AddUntyped(string path) + { + ConfigurationReference config = ConfigurationReference.CreateUntyped(path); + _references.Add(config.Path, config.Type); + } + + /// + /// Get the type of the resource at the given path + /// + /// The path of the resource + /// The path is not editable or readable + /// No configuration exists for the given path + /// The type of the resource at the given path + 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 configDic = config; configDic[path] = value; JObject obj = JObject.FromObject(config); @@ -104,7 +126,7 @@ namespace Kyoo.Controllers /// The configuration to transform /// A strongly typed representation of the configuration. [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 /// /// The section to convert /// The converted section - 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()) diff --git a/Kyoo/Controllers/PluginManager.cs b/Kyoo/Controllers/PluginManager.cs index cd0a38f6..230480c6 100644 --- a/Kyoo/Controllers/PluginManager.cs +++ b/Kyoo/Controllers/PluginManager.cs @@ -22,7 +22,7 @@ namespace Kyoo.Controllers /// /// The service provider. It allow plugin's activation. /// - private readonly IServiceProvider _provider; + private IServiceProvider _provider; /// /// The configuration to get the plugin's directory. /// @@ -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; + } + /// public T GetPlugin(string name) @@ -128,6 +135,7 @@ namespace Kyoo.Controllers _logger.LogInformation("Plugin enabled: {Plugins}", _plugins.Select(x => x.Name)); } + /// 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); + } } /// diff --git a/Kyoo/Controllers/TaskManager.cs b/Kyoo/Controllers/TaskManager.cs index a10875c1..980bbab0 100644 --- a/Kyoo/Controllers/TaskManager.cs +++ b/Kyoo/Controllers/TaskManager.cs @@ -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); } - /// - /// Inject services into the marked properties of the given object. - /// - /// The object to inject - /// The function used to retrieve services. (The function is called immediately) - private static void InjectServices(ITask obj, [InstantHandle] Func retrieve) - { - IEnumerable properties = obj.GetType().GetProperties() - .Where(x => x.GetCustomAttribute() != null) - .Where(x => x.CanWrite); - - foreach (PropertyInfo property in properties) - property.SetValue(obj, retrieve(property.PropertyType)); - } - /// /// Start tasks that are scheduled for start. /// diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs index f19ba596..4d4baf7d 100644 --- a/Kyoo/CoreModule.cs +++ b/Kyoo/CoreModule.cs @@ -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. /// private readonly IConfiguration _configuration; + + /// + /// The configuration manager used to register typed/untyped implementations. + /// + [Injected] public IConfigurationManager ConfigurationManager { private get; set; } /// @@ -136,14 +142,9 @@ namespace Kyoo string publicUrl = _configuration.GetPublicUrl(); services.Configure(_configuration.GetSection(BasicOptions.Path)); - services.AddConfiguration(BasicOptions.Path); services.Configure(_configuration.GetSection(TaskOptions.Path)); - services.AddConfiguration(TaskOptions.Path); services.Configure(_configuration.GetSection(MediaOptions.Path)); - services.AddConfiguration(MediaOptions.Path); - services.AddUntypedConfiguration("database"); - services.AddUntypedConfiguration("logging"); - + services.AddControllers() .AddNewtonsoftJson(x => { @@ -157,6 +158,12 @@ namespace Kyoo /// public void ConfigureAspNet(IApplicationBuilder app) { + ConfigurationManager.AddTyped(BasicOptions.Path); + ConfigurationManager.AddTyped(TaskOptions.Path); + ConfigurationManager.AddTyped(MediaOptions.Path); + ConfigurationManager.AddUntyped("database"); + ConfigurationManager.AddUntyped("logging"); + FileExtensionContentTypeProvider contentTypeProvider = new(); contentTypeProvider.Mappings[".data"] = "application/octet-stream"; app.UseStaticFiles(new StaticFileOptions diff --git a/Kyoo/Helper.cs b/Kyoo/Helper.cs new file mode 100644 index 00000000..244f6eec --- /dev/null +++ b/Kyoo/Helper.cs @@ -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 + { + /// + /// Inject services into the marked properties of the given object. + /// + /// The object to inject + /// The function used to retrieve services. (The function is called immediately) + public static void InjectServices(object obj, [InstantHandle] Func retrieve) + { + IEnumerable properties = obj.GetType().GetProperties() + .Where(x => x.GetCustomAttribute() != null) + .Where(x => x.CanWrite); + + foreach (PropertyInfo property in properties) + property.SetValue(obj, retrieve(property.PropertyType)); + } + + /// + /// 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 System.Net.Http.Json namespace when .net6 + /// gets released. + /// + /// The http server to use. + /// The url to retrieve + /// The type of object to convert + /// A T representing the json contained at the given url. + public static async Task GetFromJsonAsync(this HttpClient client, string url) + { + HttpResponseMessage ret = await client.GetAsync(url); + ret.EnsureSuccessStatusCode(); + string content = await ret.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(content); + } + } +} \ No newline at end of file diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 9d3c19dd..04a24408 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -71,7 +71,6 @@ namespace Kyoo services.AddHttpClient(); - // services.AddTransient(typeof(Lazy<>), typeof(LazyDi<>)); _plugins.ConfigureServices(services); } @@ -87,7 +86,7 @@ namespace Kyoo /// /// The asp net host to configure /// The host environment (is the app in development mode?) - 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 =>