Plugins: Reworking ConfigureAspnet to use steps with prorities. Adding a configuration dictionary.

This commit is contained in:
Zoe Roux 2021-08-13 16:36:27 +02:00
parent b67e0a629e
commit d1b3769f4f
15 changed files with 348 additions and 235 deletions

View File

@ -9,7 +9,6 @@ using IdentityServer4.Services;
using Kyoo.Authentication.Models; using Kyoo.Authentication.Models;
using Kyoo.Authentication.Views; using Kyoo.Authentication.Views;
using Kyoo.Controllers; using Kyoo.Controllers;
using Kyoo.Models.Attributes;
using Kyoo.Models.Permissions; using Kyoo.Models.Permissions;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@ -38,6 +37,12 @@ namespace Kyoo.Authentication
/// <inheritdoc /> /// <inheritdoc />
public string Description => "Enable OpenID authentication for Kyoo."; public string Description => "Enable OpenID authentication for Kyoo.";
/// <inheritdoc />
public Dictionary<string, Type> Configuration => new()
{
{ AuthenticationOption.Path, typeof(AuthenticationOption) }
};
/// <summary> /// <summary>
/// The configuration to use. /// The configuration to use.
@ -53,11 +58,6 @@ namespace Kyoo.Authentication
/// The environment information to check if the app runs in debug mode /// The environment information to check if the app runs in debug mode
/// </summary> /// </summary>
private readonly IWebHostEnvironment _environment; private readonly IWebHostEnvironment _environment;
/// <summary>
/// The configuration manager used to register typed/untyped implementations.
/// </summary>
[Injected] public IConfigurationManager ConfigurationManager { private get; set; }
/// <summary> /// <summary>
@ -88,10 +88,6 @@ namespace Kyoo.Authentication
// TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos) // TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos)
// TODO Check if tokens should be stored. // TODO Check if tokens should be stored.
services.Configure<PermissionOption>(_configuration.GetSection(PermissionOption.Path));
services.Configure<CertificateOption>(_configuration.GetSection(CertificateOption.Path));
services.Configure<AuthenticationOption>(_configuration.GetSection(AuthenticationOption.Path));
List<Client> clients = new(); List<Client> clients = new();
_configuration.GetSection("authentication:clients").Bind(clients); _configuration.GetSection("authentication:clients").Bind(clients);
@ -129,37 +125,43 @@ namespace Kyoo.Authentication
} }
/// <inheritdoc /> /// <inheritdoc />
public void ConfigureAspNet(IApplicationBuilder app) public IEnumerable<IStartupAction> ConfigureSteps => new IStartupAction[]
{ {
ConfigurationManager.AddTyped<AuthenticationOption>(AuthenticationOption.Path); SA.New<IApplicationBuilder>(app =>
app.UseCookiePolicy(new CookiePolicyOptions
{ {
MinimumSameSitePolicy = SameSiteMode.Strict PhysicalFileProvider provider = new(Path.Combine(
}); Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
app.UseAuthentication(); "login"));
app.Use((ctx, next) => app.UseDefaultFiles(new DefaultFilesOptions
{
RequestPath = new PathString("/login"),
FileProvider = provider,
RedirectToAppendTrailingSlash = true
});
app.UseStaticFiles(new StaticFileOptions
{
RequestPath = new PathString("/login"),
FileProvider = provider
});
}, SA.StaticFiles),
SA.New<IApplicationBuilder>(app =>
{ {
ctx.SetIdentityServerOrigin(_configuration.GetPublicUrl()); app.UseCookiePolicy(new CookiePolicyOptions
return next(); {
}); MinimumSameSitePolicy = SameSiteMode.Strict
app.UseIdentityServer(); });
app.UseAuthorization(); app.UseAuthentication();
}, SA.Authentication),
PhysicalFileProvider provider = new(Path.Combine( SA.New<IApplicationBuilder>(app =>
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
"login"));
app.UseDefaultFiles(new DefaultFilesOptions
{ {
RequestPath = new PathString("/login"), app.Use((ctx, next) =>
FileProvider = provider, {
RedirectToAppendTrailingSlash = true ctx.SetIdentityServerOrigin(_configuration.GetPublicUrl());
}); return next();
app.UseStaticFiles(new StaticFileOptions });
{ app.UseIdentityServer();
RequestPath = new PathString("/login"), }, SA.Endpoint),
FileProvider = provider SA.New<IApplicationBuilder>(app => app.UseAuthorization(), SA.Authorization)
}); };
}
} }
} }

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic;
using Autofac; using Autofac;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Controllers namespace Kyoo.Controllers
@ -41,8 +41,26 @@ namespace Kyoo.Controllers
/// By default, a plugin is always enabled. This method can be overriden to change this behavior. /// By default, a plugin is always enabled. This method can be overriden to change this behavior.
/// </remarks> /// </remarks>
virtual bool Enabled => true; virtual bool Enabled => true;
/// <summary>
/// A list of types that will be available via the IOptions interfaces and will be listed inside
/// an IConfiguration. If a field should be loosely typed, <see cref="Dictionary{TKey,TValue}"/> or <c>null</c>
/// can be specified.
/// </summary>
/// <remarks>
/// All use of the configuration must be specified here and not registered elsewhere, if a type is registered
/// elsewhere the configuration won't be editable via the <see cref="IConfigurationManager"/> and all values
/// will be discarded on edit.
/// </remarks>
Dictionary<string, Type> Configuration { get; }
/// <summary> /// <summary>
/// An optional configuration step to allow a plugin to change asp net configurations.
/// </summary>
/// <seealso cref="SA"/>
virtual IEnumerable<IStartupAction> ConfigureSteps => ArraySegment<IStartupAction>.Empty;
/// <summary>
/// A configure method that will be run on plugin's startup. /// A configure method that will be run on plugin's startup.
/// </summary> /// </summary>
/// <param name="builder">The autofac service container to register services.</param> /// <param name="builder">The autofac service container to register services.</param>
@ -61,20 +79,6 @@ namespace Kyoo.Controllers
{ {
// Skipped // Skipped
} }
/// <summary>
/// An optional configuration step to allow a plugin to change asp net configurations.
/// WARNING: This is only called on Kyoo's startup so you must restart the app to apply this changes.
/// </summary>
/// <param name="app">
/// The Asp.Net application builder. On most case it is not needed but you can use it to
/// add asp net functionalities.
/// </param>
void ConfigureAspNet(IApplicationBuilder app)
{
// Skipped
}
/// <summary> /// <summary>
/// An optional function to execute and initialize your plugin. /// An optional function to execute and initialize your plugin.

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Autofac; using Autofac;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Controllers namespace Kyoo.Controllers
@ -63,11 +62,5 @@ namespace Kyoo.Controllers
/// </summary> /// </summary>
/// <param name="services">The service collection to populate</param> /// <param name="services">The service collection to populate</param>
public void ConfigureServices(IServiceCollection services); public void ConfigureServices(IServiceCollection services);
/// <summary>
/// Configure an asp net application applying plugins policies.
/// </summary>
/// <param name="app">The asp net application to configure</param>
public void ConfigureAspnet(IApplicationBuilder app);
} }
} }

View File

@ -5,7 +5,6 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Attributes;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
namespace Kyoo.Controllers namespace Kyoo.Controllers
@ -182,11 +181,6 @@ namespace Kyoo.Controllers
/// <param name="cancellationToken">A token to request the task's cancellation. /// <param name="cancellationToken">A token to request the task's cancellation.
/// If this task is not cancelled quickly, it might be killed by the runner. /// If this task is not cancelled quickly, it might be killed by the runner.
/// </param> /// </param>
/// <remarks>
/// Your task can have any service as a public field and use the <see cref="InjectedAttribute"/>,
/// they will be set to an available service from the service container before calling this method.
/// They also will be removed after this method return (or throw) to prevent dangling services.
/// </remarks>
/// <exception cref="TaskFailedException"> /// <exception cref="TaskFailedException">
/// An exception meaning that the task has failed for handled reasons like invalid arguments, /// An exception meaning that the task has failed for handled reasons like invalid arguments,
/// invalid environment, missing plugins or failures not related to a default in the code. /// invalid environment, missing plugins or failures not related to a default in the code.

View File

@ -0,0 +1,221 @@
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Controllers
{
/// <summary>
/// A list of constant priorities used for <see cref="IStartupAction"/>'s <see cref="IStartupAction.Priority"/>.
/// It also contains helper methods for creating new <see cref="StartupAction"/>.
/// </summary>
public static class SA
{
public const int Before = 5000;
public const int Routing = 4000;
public const int StaticFiles = 3000;
public const int Authentication = 2000;
public const int Authorization = 1000;
public const int Endpoint = 0;
public const int After = -1000;
/// <summary>
/// Create a new <see cref="StartupAction"/>.
/// </summary>
/// <param name="action">The action to run</param>
/// <param name="priority">The priority of the new action</param>
/// <returns>A new <see cref="StartupAction"/></returns>
public static StartupAction New(Action action, int priority)
=> new(action, priority);
/// <summary>
/// Create a new <see cref="StartupAction"/>.
/// </summary>
/// <param name="action">The action to run</param>
/// <param name="priority">The priority of the new action</param>
/// <typeparam name="T">A dependency that this action will use.</typeparam>
/// <returns>A new <see cref="StartupAction"/></returns>
public static StartupAction<T> New<T>(Action<T> action, int priority)
=> new(action, priority);
/// <summary>
/// Create a new <see cref="StartupAction"/>.
/// </summary>
/// <param name="action">The action to run</param>
/// <param name="priority">The priority of the new action</param>
/// <typeparam name="T">A dependency that this action will use.</typeparam>
/// <typeparam name="T2">A second dependency that this action will use.</typeparam>
/// <returns>A new <see cref="StartupAction"/></returns>
public static StartupAction<T, T2> New<T, T2>(Action<T, T2> action, int priority)
=> new(action, priority);
/// <summary>
/// Create a new <see cref="StartupAction"/>.
/// </summary>
/// <param name="action">The action to run</param>
/// <param name="priority">The priority of the new action</param>
/// <typeparam name="T">A dependency that this action will use.</typeparam>
/// <typeparam name="T2">A second dependency that this action will use.</typeparam>
/// <typeparam name="T3">A third dependency that this action will use.</typeparam>
/// <returns>A new <see cref="StartupAction"/></returns>
public static StartupAction<T, T2, T3> New<T, T2, T3>(Action<T, T2, T3> action, int priority)
=> new(action, priority);
}
/// <summary>
/// An action executed on kyoo's startup to initialize the asp-net container.
/// </summary>
/// <remarks>
/// This is the base interface, see <see cref="StartupAction"/> for a simpler use of this.
/// </remarks>
public interface IStartupAction
{
/// <summary>
/// The priority of this action. The actions will be executed on descending priority order.
/// If two actions have the same priority, their order is undefined.
/// </summary>
int Priority { get; }
/// <summary>
/// Run this action to configure the container, a service provider containing all services can be used.
/// </summary>
/// <param name="provider">The service provider containing all services can be used.</param>
void Run(IServiceProvider provider);
}
/// <summary>
/// A <see cref="IStartupAction"/> with no dependencies.
/// </summary>
public class StartupAction : IStartupAction
{
/// <summary>
/// The action to execute at startup.
/// </summary>
private readonly Action _action;
/// <inheritdoc />
public int Priority { get; }
/// <summary>
/// Create a new <see cref="StartupAction"/>.
/// </summary>
/// <param name="action">The action to execute on startup.</param>
/// <param name="priority">The priority of this action (see <see cref="Priority"/>).</param>
public StartupAction(Action action, int priority)
{
_action = action;
Priority = priority;
}
/// <inheritdoc />
public void Run(IServiceProvider provider)
{
_action.Invoke();
}
}
/// <summary>
/// A <see cref="IStartupAction"/> with one dependencies.
/// </summary>
/// <typeparam name="T">The dependency to use.</typeparam>
public class StartupAction<T> : IStartupAction
{
/// <summary>
/// The action to execute at startup.
/// </summary>
private readonly Action<T> _action;
/// <inheritdoc />
public int Priority { get; }
/// <summary>
/// Create a new <see cref="StartupAction{T}"/>.
/// </summary>
/// <param name="action">The action to execute on startup.</param>
/// <param name="priority">The priority of this action (see <see cref="Priority"/>).</param>
public StartupAction(Action<T> action, int priority)
{
_action = action;
Priority = priority;
}
/// <inheritdoc />
public void Run(IServiceProvider provider)
{
_action.Invoke(provider.GetRequiredService<T>());
}
}
/// <summary>
/// A <see cref="IStartupAction"/> with two dependencies.
/// </summary>
/// <typeparam name="T">The dependency to use.</typeparam>
/// <typeparam name="T2">The second dependency to use.</typeparam>
public class StartupAction<T, T2> : IStartupAction
{
/// <summary>
/// The action to execute at startup.
/// </summary>
private readonly Action<T, T2> _action;
/// <inheritdoc />
public int Priority { get; }
/// <summary>
/// Create a new <see cref="StartupAction{T, T2}"/>.
/// </summary>
/// <param name="action">The action to execute on startup.</param>
/// <param name="priority">The priority of this action (see <see cref="Priority"/>).</param>
public StartupAction(Action<T, T2> action, int priority)
{
_action = action;
Priority = priority;
}
/// <inheritdoc />
public void Run(IServiceProvider provider)
{
_action.Invoke(
provider.GetRequiredService<T>(),
provider.GetRequiredService<T2>()
);
}
}
/// <summary>
/// A <see cref="IStartupAction"/> with three dependencies.
/// </summary>
/// <typeparam name="T">The dependency to use.</typeparam>
/// <typeparam name="T2">The second dependency to use.</typeparam>
/// <typeparam name="T3">The third dependency to use.</typeparam>
public class StartupAction<T, T2, T3> : IStartupAction
{
/// <summary>
/// The action to execute at startup.
/// </summary>
private readonly Action<T, T2, T3> _action;
/// <inheritdoc />
public int Priority { get; }
/// <summary>
/// Create a new <see cref="StartupAction{T, T2, T3}"/>.
/// </summary>
/// <param name="action">The action to execute on startup.</param>
/// <param name="priority">The priority of this action (see <see cref="Priority"/>).</param>
public StartupAction(Action<T, T2, T3> action, int priority)
{
_action = action;
Priority = priority;
}
/// <inheritdoc />
public void Run(IServiceProvider provider)
{
_action.Invoke(
provider.GetRequiredService<T>(),
provider.GetRequiredService<T2>(),
provider.GetRequiredService<T3>()
);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
using JetBrains.Annotations;
using Kyoo.Controllers;
namespace Kyoo.Models.Attributes
{
/// <summary>
/// An attribute to inform that the service will be injected automatically by a service provider.
/// </summary>
/// <remarks>
/// It should only be used on <see cref="IPlugin"/> and it will be injected before
/// calling <see cref="IPlugin.ConfigureAspNet"/>.
/// </remarks>
[AttributeUsage(AttributeTargets.Property)]
[MeansImplicitUse(ImplicitUseKindFlags.Assign)]
public class InjectedAttribute : Attribute { }
}

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Kyoo.Controllers; using Kyoo.Controllers;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -22,6 +23,9 @@ namespace Kyoo.Postgresql
/// <inheritdoc /> /// <inheritdoc />
public string Description => "A database context for postgresql."; public string Description => "A database context for postgresql.";
/// <inheritdoc />
public Dictionary<string, Type> Configuration => new();
/// <inheritdoc /> /// <inheritdoc />
public bool Enabled => _configuration.GetSelectedDatabase() == "postgres"; public bool Enabled => _configuration.GetSelectedDatabase() == "postgres";

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Kyoo.Controllers; using Kyoo.Controllers;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -22,6 +23,9 @@ namespace Kyoo.SqLite
/// <inheritdoc /> /// <inheritdoc />
public string Description => "A database context for sqlite."; public string Description => "A database context for sqlite.";
/// <inheritdoc />
public Dictionary<string, Type> Configuration => new();
/// <inheritdoc /> /// <inheritdoc />
public bool Enabled => _configuration.GetSelectedDatabase() == "sqlite"; public bool Enabled => _configuration.GetSelectedDatabase() == "sqlite";

View File

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using Autofac; using Autofac;
using Kyoo.Controllers; using Kyoo.Controllers;
using Kyoo.Models.Attributes;
using Kyoo.TheMovieDb.Models; using Kyoo.TheMovieDb.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.TheMovieDb namespace Kyoo.TheMovieDb
{ {
@ -21,45 +19,17 @@ namespace Kyoo.TheMovieDb
/// <inheritdoc /> /// <inheritdoc />
public string Description => "A metadata provider for TheMovieDB."; public string Description => "A metadata provider for TheMovieDB.";
/// <summary>
/// The configuration to use.
/// </summary>
private readonly IConfiguration _configuration;
/// <summary> /// <inheritdoc />
/// The configuration manager used to register typed/untyped implementations. public Dictionary<string, Type> Configuration => new()
/// </summary>
[Injected] public IConfigurationManager ConfigurationManager { private get; set; }
/// <summary>
/// Create a new tmdb module instance and use the given configuration.
/// </summary>
/// <param name="configuration">The configuration to use</param>
public PluginTmdb(IConfiguration configuration)
{ {
_configuration = configuration; { TheMovieDbOptions.Path, typeof(TheMovieDbOptions) }
} };
/// <inheritdoc /> /// <inheritdoc />
public void Configure(ContainerBuilder builder) public void Configure(ContainerBuilder builder)
{ {
builder.RegisterProvider<TheMovieDbProvider>(); builder.RegisterProvider<TheMovieDbProvider>();
} }
/// <inheritdoc />
public void Configure(IServiceCollection services)
{
services.Configure<TheMovieDbOptions>(_configuration.GetSection(TheMovieDbOptions.Path));
}
/// <inheritdoc />
public void ConfigureAspNet(IApplicationBuilder app)
{
ConfigurationManager.AddTyped<TheMovieDbOptions>(TheMovieDbOptions.Path);
}
} }
} }

View File

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using Autofac; using Autofac;
using Kyoo.Controllers; using Kyoo.Controllers;
using Kyoo.Models.Attributes;
using Kyoo.TheTvdb.Models; using Kyoo.TheTvdb.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TvDbSharper; using TvDbSharper;
namespace Kyoo.TheTvdb namespace Kyoo.TheTvdb
@ -23,44 +21,17 @@ namespace Kyoo.TheTvdb
/// <inheritdoc /> /// <inheritdoc />
public string Description => "A metadata provider for The TVDB."; public string Description => "A metadata provider for The TVDB.";
/// <summary> /// <inheritdoc />
/// The configuration to use. public Dictionary<string, Type> Configuration => new()
/// </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; { TvdbOption.Path, typeof(TvdbOption) }
} };
/// <inheritdoc /> /// <inheritdoc />
public void Configure(ContainerBuilder builder) public void Configure(ContainerBuilder builder)
{ {
builder.RegisterType<TvDbClient>().As<ITvDbClient>(); builder.RegisterType<TvDbClient>().As<ITvDbClient>();
builder.RegisterProvider<ProviderTvdb>(); builder.RegisterProvider<ProviderTvdb>();
} }
/// <inheritdoc />
public void Configure(IServiceCollection services)
{
services.Configure<TvdbOption>(_configuration.GetSection(TvdbOption.Path));
}
/// <inheritdoc />
public void ConfigureAspNet(IApplicationBuilder app)
{
ConfigurationManager.AddTyped<TvdbOption>(TvdbOption.Path);
}
} }
} }

View File

@ -6,7 +6,6 @@ using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using Autofac; using Autofac;
using Kyoo.Models.Options; using Kyoo.Models.Options;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -148,18 +147,6 @@ namespace Kyoo.Controllers
plugin.Configure(services); plugin.Configure(services);
} }
/// <inheritdoc />
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);
Helper.InjectServices(plugin, _ => null);
}
}
/// <summary> /// <summary>
/// A custom <see cref="AssemblyLoadContext"/> to load plugin's dependency if they are on the same folder. /// A custom <see cref="AssemblyLoadContext"/> to load plugin's dependency if they are on the same folder.
/// </summary> /// </summary>

View File

@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using Autofac; using Autofac;
using Autofac.Core; using Autofac.Core;
using Autofac.Core.Registration; using Autofac.Core.Registration;
using Kyoo.Controllers; using Kyoo.Controllers;
using Kyoo.Models.Attributes;
using Kyoo.Models.Options; using Kyoo.Models.Options;
using Kyoo.Models.Permissions; using Kyoo.Models.Permissions;
using Kyoo.Tasks; using Kyoo.Tasks;
@ -13,6 +13,7 @@ using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
namespace Kyoo namespace Kyoo
{ {
@ -30,18 +31,23 @@ namespace Kyoo
/// <inheritdoc /> /// <inheritdoc />
public string Description => "The core module containing default implementations."; public string Description => "The core module containing default implementations.";
/// <inheritdoc />
public Dictionary<string, Type> Configuration => new()
{
{ BasicOptions.Path, typeof(BasicOptions) },
{ TaskOptions.Path, typeof(TaskOptions) },
{ MediaOptions.Path, typeof(MediaOptions) },
{ "database", null },
{ "logging", null }
};
/// <summary> /// <summary>
/// The configuration to use. /// The configuration to use.
/// </summary> /// </summary>
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
/// <summary>
/// The configuration manager used to register typed/untyped implementations.
/// </summary>
[Injected] public IConfigurationManager ConfigurationManager { private get; set; }
/// <summary> /// <summary>
/// Create a new core module instance and use the given configuration. /// Create a new core module instance and use the given configuration.
/// </summary> /// </summary>
@ -99,10 +105,6 @@ namespace Kyoo
{ {
string publicUrl = _configuration.GetPublicUrl(); string publicUrl = _configuration.GetPublicUrl();
services.Configure<BasicOptions>(_configuration.GetSection(BasicOptions.Path));
services.Configure<TaskOptions>(_configuration.GetSection(TaskOptions.Path));
services.Configure<MediaOptions>(_configuration.GetSection(MediaOptions.Path));
services.AddControllers() services.AddControllers()
.AddNewtonsoftJson(x => .AddNewtonsoftJson(x =>
{ {
@ -114,26 +116,30 @@ namespace Kyoo
} }
/// <inheritdoc /> /// <inheritdoc />
public void ConfigureAspNet(IApplicationBuilder app) public IEnumerable<IStartupAction> ConfigureSteps => new IStartupAction[]
{ {
ConfigurationManager.AddTyped<BasicOptions>(BasicOptions.Path); SA.New<IApplicationBuilder, IHostEnvironment>((app, env) =>
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
{ {
ContentTypeProvider = contentTypeProvider, if (env.IsDevelopment())
FileProvider = new PhysicalFileProvider(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot")) app.UseDeveloperExceptionPage();
}); else
{
app.UseEndpoints(endpoints => app.UseExceptionHandler("/error");
app.UseHsts();
}
}, SA.Before),
SA.New<IApplicationBuilder>(app => app.UseRouting(), SA.Routing),
SA.New<IApplicationBuilder>(app =>
{ {
endpoints.MapControllers(); FileExtensionContentTypeProvider contentTypeProvider = new();
}); contentTypeProvider.Mappings[".data"] = "application/octet-stream";
} app.UseStaticFiles(new StaticFileOptions
{
ContentTypeProvider = contentTypeProvider,
FileProvider = new PhysicalFileProvider(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot"))
});
}, SA.StaticFiles),
SA.New<IApplicationBuilder>(app => app.UseEndpoints(x => x.MapControllers()), SA.Endpoint)
};
} }
} }

View File

@ -1,32 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using Kyoo.Models.Attributes;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Kyoo namespace Kyoo
{ {
public static class Helper 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> /// <summary>
/// An helper method to get json content from an http server. This is a temporary thing and will probably be /// 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 /// replaced by a call to the function of the same name in the <c>System.Net.Http.Json</c> namespace when .net6

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using Autofac; using Autofac;
using Autofac.Extras.AttributeMetadata; using Autofac.Extras.AttributeMetadata;
using Kyoo.Authentication; using Kyoo.Authentication;
@ -90,18 +91,9 @@ namespace Kyoo
/// <param name="env">The host environment (is the app in development mode?)</param> /// <param name="env">The host environment (is the app in development mode?)</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider provider) public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider provider)
{ {
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
{
app.UseExceptionHandler("/error");
app.UseHsts();
}
if (!env.IsDevelopment()) if (!env.IsDevelopment())
app.UseSpaStaticFiles(); app.UseSpaStaticFiles();
app.UseRouting();
app.Use((ctx, next) => app.Use((ctx, next) =>
{ {
ctx.Response.Headers.Remove("X-Powered-By"); ctx.Response.Headers.Remove("X-Powered-By");
@ -116,9 +108,11 @@ namespace Kyoo
}); });
app.UseResponseCompression(); app.UseResponseCompression();
if (_plugins is PluginManager manager) IEnumerable<IStartupAction> steps = _plugins.GetAllPlugins()
manager.SetProvider(provider); .SelectMany(x => x.ConfigureSteps)
_plugins.ConfigureAspnet(app); .OrderByDescending(x => x.Priority);
foreach (IStartupAction step in steps)
step.Run(provider);
app.UseSpa(spa => app.UseSpa(spa =>
{ {

View File

@ -28,8 +28,8 @@ namespace Kyoo
[SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse")] [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse")]
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
if (!File.Exists("./settings.json")) if (!File.Exists(JsonConfigPath))
File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "settings.json"), "settings.json"); File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, JsonConfigPath), JsonConfigPath);
IHostBuilder builder = CreateWebHostBuilder(args); IHostBuilder builder = CreateWebHostBuilder(args);
@ -77,7 +77,8 @@ namespace Kyoo
/// <returns>The modified configuration builder</returns> /// <returns>The modified configuration builder</returns>
private static IConfigurationBuilder SetupConfig(IConfigurationBuilder builder, string[] args) private static IConfigurationBuilder SetupConfig(IConfigurationBuilder builder, string[] args)
{ {
return builder.AddJsonFile(JsonConfigPath, false, true) return builder.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile(JsonConfigPath, false, true)
.AddEnvironmentVariables() .AddEnvironmentVariables()
.AddCommandLine(args); .AddCommandLine(args);
} }