diff --git a/.gitmodules b/.gitmodules index 616ea2bb..3085af6a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,6 +3,6 @@ url = ../Kyoo.Transcoder.git branch = master [submodule "WebApp"] - path = Kyoo.WebApp + path = Kyoo.WebApp/Front url = ../Kyoo.WebApp.git branch = master diff --git a/Kyoo.Authentication/AuthenticationModule.cs b/Kyoo.Authentication/AuthenticationModule.cs index fe7e07e3..26dc5fc8 100644 --- a/Kyoo.Authentication/AuthenticationModule.cs +++ b/Kyoo.Authentication/AuthenticationModule.cs @@ -53,9 +53,9 @@ namespace Kyoo.Authentication private readonly IConfiguration _configuration; /// - /// A logger factory to allow IdentityServer to log things. + /// The logger used to allow IdentityServer to log things. /// - private readonly ILoggerFactory _loggerFactory; + private readonly ILogger _logger; /// /// The environment information to check if the app runs in debug mode @@ -67,14 +67,14 @@ namespace Kyoo.Authentication /// Create a new authentication module instance and use the given configuration and environment. /// /// The configuration to use - /// The logger factory to allow IdentityServer to log things + /// The logger used to allow IdentityServer to log things /// The environment information to check if the app runs in debug mode public AuthenticationModule(IConfiguration configuration, - ILoggerFactory loggerFactory, + ILogger logger, IWebHostEnvironment environment) { _configuration = configuration; - _loggerFactory = loggerFactory; + _logger = logger; _environment = environment; } @@ -83,7 +83,7 @@ namespace Kyoo.Authentication { builder.RegisterType().As().SingleInstance(); - DefaultCorsPolicyService cors = new(_loggerFactory.CreateLogger()) + DefaultCorsPolicyService cors = new(_logger) { AllowedOrigins = { new Uri(_configuration.GetPublicUrl()).GetLeftPart(UriPartial.Authority) } }; diff --git a/Kyoo.Common/Controllers/IPlugin.cs b/Kyoo.Common/Controllers/IPlugin.cs index 4d86f06c..090774a7 100644 --- a/Kyoo.Common/Controllers/IPlugin.cs +++ b/Kyoo.Common/Controllers/IPlugin.cs @@ -93,5 +93,14 @@ namespace Kyoo.Controllers { // Skipped } + + /// + /// An optional callback function called when the startups ends and this plugin has been flagged has disabled. + /// It allow a plugin to log an error or warning message to inform why it has been disabled. + /// + void Disabled() + { + // Skipped + } } } \ No newline at end of file diff --git a/Kyoo.Common/Utility/Utility.cs b/Kyoo.Common/Utility/Utility.cs index 72d25fd5..ab4fc92d 100644 --- a/Kyoo.Common/Utility/Utility.cs +++ b/Kyoo.Common/Utility/Utility.cs @@ -155,7 +155,8 @@ namespace Kyoo IEnumerable types = genericType.IsInterface ? type.GetInterfaces() : type.GetInheritanceTree(); - return types.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); + return types.Prepend(type) + .Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); } /// @@ -179,7 +180,8 @@ namespace Kyoo IEnumerable types = genericType.IsInterface ? type.GetInterfaces() : type.GetInheritanceTree(); - return types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); + return types.Prepend(type) + .FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); } /// diff --git a/Kyoo.WebApp b/Kyoo.WebApp deleted file mode 160000 index 49cf0c3d..00000000 --- a/Kyoo.WebApp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 49cf0c3d17f889f40fa9adbb383edfc0d2c99779 diff --git a/Kyoo.WebApp/Front b/Kyoo.WebApp/Front new file mode 160000 index 00000000..dca10903 --- /dev/null +++ b/Kyoo.WebApp/Front @@ -0,0 +1 @@ +Subproject commit dca10903ff54a8999732695b5c2a0a5c94f85200 diff --git a/Kyoo.WebApp/Kyoo.WebApp.csproj b/Kyoo.WebApp/Kyoo.WebApp.csproj new file mode 100644 index 00000000..dcf3c30e --- /dev/null +++ b/Kyoo.WebApp/Kyoo.WebApp.csproj @@ -0,0 +1,61 @@ + + + net5.0 + true + Latest + false + $(DefaultItemExcludes);$(SpaRoot)node_modules/** + Front/ + + + false + + SDG + Zoe Roux + https://github.com/AnonymusRaccoon/Kyoo + default + + + + + + + + + + + + + + wwwroot/%(RecursiveDir)%(Filename)%(Extension) + Always + + + + + wwwroot/%(RecursiveDir)%(Filename)%(Extension) + Always + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Kyoo.WebApp/WebAppModule.cs b/Kyoo.WebApp/WebAppModule.cs new file mode 100644 index 00000000..889ec177 --- /dev/null +++ b/Kyoo.WebApp/WebAppModule.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Kyoo.Controllers; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.SpaServices.AngularCli; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Kyoo.WebApp +{ + /// + /// A module to enable the web-app (the front-end of kyoo). + /// + public class WebAppModule : IPlugin + { + /// + public string Slug => "webapp"; + + /// + public string Name => "WebApp"; + + /// + public string Description => "A module to enable the web-app (the front-end of kyoo)."; + + /// + public Dictionary Configuration => new(); + + /// + public bool Enabled => Directory.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot")); + + + /// + /// A logger only used to inform the user if the webapp could not be enabled. + /// + private readonly ILogger _logger; + + /// + /// Create a new . + /// + /// A logger only used to inform the user if the webapp could not be enabled. + public WebAppModule(ILogger logger) + { + _logger = logger; + } + + /// + public void Disabled() + { + _logger.LogError("The web app files could not be found, it will be disabled. " + + "If you cloned the project, you probably forgot to use the --recurse flag"); + } + + /// + public void Configure(IServiceCollection services) + { + services.AddSpaStaticFiles(x => + { + x.RootPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot"); + }); + } + + /// + public IEnumerable ConfigureSteps => new IStartupAction[] + { + SA.New((app, env) => + { + if (!env.IsDevelopment()) + app.UseSpaStaticFiles(); + }, SA.StaticFiles), + SA.New((app, contentTypeProvider) => + { + app.UseStaticFiles(new StaticFileOptions + { + ContentTypeProvider = contentTypeProvider, + FileProvider = new PhysicalFileProvider(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot")) + }); + }, SA.StaticFiles), + SA.New(app => + { + app.Use((ctx, next) => + { + ctx.Response.Headers.Remove("X-Powered-By"); + ctx.Response.Headers.Remove("Server"); + ctx.Response.Headers.Add("Feature-Policy", "autoplay 'self'; fullscreen"); + ctx.Response.Headers.Add("Content-Security-Policy", "default-src 'self' blob:; script-src 'self' blob: 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; frame-src 'self'"); + ctx.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + ctx.Response.Headers.Add("Referrer-Policy", "no-referrer"); + ctx.Response.Headers.Add("Access-Control-Allow-Origin", "null"); + ctx.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + return next(); + }); + }, SA.Endpoint - 499), + SA.New((app, env) => + { + app.UseSpa(spa => + { + spa.Options.SourcePath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Kyoo.WebApp", "Front"); + + if (env.IsDevelopment()) + spa.UseAngularCliServer("start"); + }); + }, SA.Endpoint - 500) + }; + } +} \ No newline at end of file diff --git a/Kyoo/Controllers/PluginManager.cs b/Kyoo/Controllers/PluginManager.cs index 2727124c..c55aa906 100644 --- a/Kyoo/Controllers/PluginManager.cs +++ b/Kyoo/Controllers/PluginManager.cs @@ -110,12 +110,15 @@ namespace Kyoo.Controllers _logger.LogTrace("Loading new plugins..."); string[] pluginsPaths = Directory.GetFiles(pluginFolder, "*.dll", SearchOption.AllDirectories); - _plugins.AddRange(plugins + IPlugin[] newPlugins = plugins .Concat(pluginsPaths.SelectMany(LoadPlugin)) .GroupBy(x => x.Name) .Select(x => x.First()) - .Where(x => x.Enabled) - ); + .ToArray(); + _plugins.AddRange(newPlugins.Where(x => x.Enabled)); + + foreach (IPlugin plugin in newPlugins.Where(x => !x.Enabled)) + plugin.Disabled(); if (!_plugins.Any()) _logger.LogInformation("No plugin enabled"); diff --git a/Kyoo/PluginsStartup.cs b/Kyoo/PluginsStartup.cs index c13c9364..4f69423d 100644 --- a/Kyoo/PluginsStartup.cs +++ b/Kyoo/PluginsStartup.cs @@ -49,7 +49,7 @@ namespace Kyoo _configuration = configuration; // TODO enable the web app only if it was build with it. _plugins.LoadPlugins( - typeof(CoreModule), + typeof(CoreModule), typeof(WebAppModule), typeof(AuthenticationModule), typeof(PostgresModule), @@ -198,8 +198,16 @@ namespace Kyoo return _hostEnvironment; if (serviceType == typeof(IConfiguration)) return _configuration; - if (serviceType == typeof(ILoggerFactory)) - return _loggerFactory; + if (serviceType.GetGenericTypeDefinition() == typeof(ILogger<>)) + { + return Utility.RunGenericMethod( + typeof(LoggerFactoryExtensions), + nameof(LoggerFactoryExtensions.CreateLogger), + serviceType.GetGenericArguments().First(), + _loggerFactory + ); + } + return null; } } diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index 4eaa4eeb..e9f21a0b 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; using Autofac.Extensions.DependencyInjection; @@ -20,54 +19,38 @@ namespace Kyoo /// The path of the json configuration of the application. /// public const string JsonConfigPath = "./settings.json"; + + /// + /// The string representation of the environment used in . + /// +#if DEBUG + private const string Environment = "Development"; +#else + private const string Environment = "Production"; +#endif /// /// Main function of the program /// /// Command line arguments - [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse")] public static async Task Main(string[] args) { if (!File.Exists(JsonConfigPath)) File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, JsonConfigPath), JsonConfigPath); - IHostBuilder builder = CreateWebHostBuilder(args); + IHost host = CreateWebHostBuilder(args) + .UseEnvironment(Environment) + .Build(); - // TODO remove ENVIRONEMENT handling and force it to the build env - - bool? debug = Environment.GetEnvironmentVariable("ENVIRONMENT")?.ToLowerInvariant() switch - { - "d" => true, - "dev" => true, - "debug" => true, - "development" => true, - "p" => false, - "prod" => false, - "production" => false, - _ => null - }; - - if (debug == null && Environment.GetEnvironmentVariable("ENVIRONMENT") != null) - { - Console.WriteLine( - $"Invalid ENVIRONMENT variable. Supported values are \"debug\" and \"prod\". Ignoring..."); - } - - #if DEBUG - debug ??= true; - #endif - - if (debug != null) - builder = builder.UseEnvironment(debug == true ? "Development" : "Production"); - try { - Console.WriteLine($"Running as {Environment.UserName}."); - await builder.Build().RunAsync(); + host.Services.GetRequiredService>() + .LogInformation("Running as {Name}", System.Environment.UserName); + await host.RunAsync(); } catch (Exception ex) { - await Console.Error.WriteLineAsync($"Unhandled exception: {ex}"); + host.Services.GetRequiredService>().LogCritical(ex, "Unhandled exception"); } } @@ -79,7 +62,7 @@ namespace Kyoo /// The modified configuration builder private static IConfigurationBuilder SetupConfig(IConfigurationBuilder builder, string[] args) { - return builder.SetBasePath(Environment.CurrentDirectory) + return builder.SetBasePath(System.Environment.CurrentDirectory) .AddJsonFile(JsonConfigPath, false, true) .AddEnvironmentVariables() .AddCommandLine(args); @@ -129,5 +112,10 @@ namespace Kyoo .UseStartup(host => PluginsStartup.FromWebHost(host, loggingConfiguration)) ); } + + /// + /// An useless class only used to have a logger in the main. + /// + private class Application {} } }