From e0e9edf4261e0900128f8239fa96a2b5bb18d9bb Mon Sep 17 00:00:00 2001 From: Sakura Akeno Isayeki Date: Sat, 15 Jul 2023 13:04:09 +0200 Subject: [PATCH] feat: Update YumeCore Dependency Injection - Updated the `Program.cs` file to use the new `AddYumeCoreServices` extension method for configuring services. - Added a new file `YumeCoreDependencyInjectionExtensions.cs` which contains the implementation of the `AddYumeCoreServices` extension method. - Added a new file `DiscordBotTokenProvider.cs` which provides the Discord bot token from either the `ICoreProperties` or environment variables. - Updated the project file to enable nullable reference types. - Updated the `YumeCore.cs` class to use dependency injection for configuring services and initializing core properties. --- src/YumeChan.ConsoleRunner/Program.cs | 21 ++- src/YumeChan.Core/Config/ICoreProperties.cs | 6 +- .../YumeCoreDependencyInjectionExtensions.cs | 64 +++++++++ .../Services/DiscordBotTokenProvider.cs | 77 ++++++++++ src/YumeChan.Core/YumeChan.Core.csproj | 1 + src/YumeChan.Core/YumeCore.cs | 134 +++--------------- src/YumeChan.NetRunner/Program.cs | 16 +-- src/YumeChan.NetRunner/Startup.cs | 10 +- 8 files changed, 188 insertions(+), 141 deletions(-) create mode 100644 src/YumeChan.Core/Infrastructure/YumeCoreDependencyInjectionExtensions.cs create mode 100644 src/YumeChan.Core/Services/DiscordBotTokenProvider.cs diff --git a/src/YumeChan.ConsoleRunner/Program.cs b/src/YumeChan.ConsoleRunner/Program.cs index 9a6030a..5aad401 100644 --- a/src/YumeChan.ConsoleRunner/Program.cs +++ b/src/YumeChan.ConsoleRunner/Program.cs @@ -38,7 +38,7 @@ public static async Task Main(string[] _) _container.RegisterInstance(new ConsoleRunnerContext(RunnerType.Console, typeof(Program).Assembly.GetName().Name, informationalVersion)); Microsoft.Extensions.Logging.ILogger logger = _container.Resolve(); - logger.LogInformation("Yume-Chan ConsoleRunner v{Version}.", informationalVersion); + logger.LogInformation("Yume-Chan ConsoleRunner v{version}.", informationalVersion); await Task.WhenAll( host.StartAsync(), @@ -50,16 +50,15 @@ public static async Task Main(string[] _) public static IHostBuilder CreateHostBuilder(UnityContainer serviceRegistry = null) => new HostBuilder() .UseUnityServiceProvider(serviceRegistry ?? new()) - .ConfigureLogging(x => x.ClearProviders()) + .ConfigureLogging(static x => x.ClearProviders()) .UseSerilog() - .ConfigureContainer((_, container) => - { - _container = container; // This assignment is necessary, as configuration only affects the child container. + .ConfigureContainer(static (_, container) => + { + _container = container; // This assignment is necessary, as configuration only affects the child container. - container.AddExtension(new LoggingExtension(new SerilogLoggerFactory())); - container.AddServices(new ServiceCollection().AddLogging(x => x.AddSerilog())); - - YumeCore.Instance.ConfigureContainer(container); - } - ); + container.AddExtension(new LoggingExtension(new SerilogLoggerFactory())); + container.AddServices(new ServiceCollection() + .AddYumeCoreServices() + .AddLogging(static x => x.AddSerilog())); + }); } \ No newline at end of file diff --git a/src/YumeChan.Core/Config/ICoreProperties.cs b/src/YumeChan.Core/Config/ICoreProperties.cs index ef50df0..fc0adb3 100644 --- a/src/YumeChan.Core/Config/ICoreProperties.cs +++ b/src/YumeChan.Core/Config/ICoreProperties.cs @@ -30,7 +30,7 @@ public interface ICoreProperties /// /// The path to the bot's working directory. /// - public string Path_Core { get; internal set; } + public string? Path_Core { get; internal set; } /// /// The path to the bot's configuration directory. @@ -38,7 +38,7 @@ public interface ICoreProperties /// /// Defaults to {Path_Core}/config". /// - public string Path_Config { get; internal set; } + public string? Path_Config { get; internal set; } /// /// The path to the bot's plugin directory. @@ -46,7 +46,7 @@ public interface ICoreProperties /// /// Defaults to {Path_Core}/plugins". /// - public string Path_Plugins { get; internal set; } + public string? Path_Plugins { get; internal set; } /// /// The prefix used to identify commands. diff --git a/src/YumeChan.Core/Infrastructure/YumeCoreDependencyInjectionExtensions.cs b/src/YumeChan.Core/Infrastructure/YumeCoreDependencyInjectionExtensions.cs new file mode 100644 index 0000000..eeeb4cc --- /dev/null +++ b/src/YumeChan.Core/Infrastructure/YumeCoreDependencyInjectionExtensions.cs @@ -0,0 +1,64 @@ +using DSharpPlus; +using Microsoft.Extensions.Logging; +using YumeChan.Core; +using YumeChan.Core.Config; +using YumeChan.Core.Services; +using YumeChan.Core.Services.Config; +using YumeChan.Core.Services.Plugins; +using YumeChan.PluginBase.Database.MongoDB; +using YumeChan.PluginBase.Database.Postgres; +using YumeChan.PluginBase.Tools; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for the YumeCore Dependency Injection. +/// +public static class YumeCoreDependencyInjectionExtensions +{ + public static IServiceCollection AddYumeCoreServices(this IServiceCollection services) + { + services.AddSingleton(static services => new(new() + { + Intents = DiscordIntents.All, + TokenType = TokenType.Bot, + Token = services.GetRequiredService().GetBotToken(), // You should find a way to get this in DI context + LoggerFactory = services.GetService(), + MinimumLogLevel = LogLevel.Information + })); + + services.AddSingleton(serviceProvider => + new(serviceProvider.GetRequiredService().Path_Plugins)); + + services.AddSingleton(PluginLifetimeListener.Instance); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(typeof(InterfaceConfigProvider<>)); + + services.AddSingleton(typeof(IMongoDatabaseProvider<>), typeof(UnifiedDatabaseProvider<>)); + services.AddSingleton(typeof(IPostgresDatabaseProvider<>), typeof(UnifiedDatabaseProvider<>)); + services.AddSingleton(typeof(IInterfaceConfigProvider<>), typeof(InterfaceConfigProvider<>)); + services.AddSingleton(typeof(IJsonConfigProvider<>), typeof(JsonConfigProvider<>)); + + services.AddSingleton(sp => + sp.GetRequiredService>() + .InitConfig("coreconfig.json", true) + .InitDefaults()); + + services.AddSingleton(sp => + sp.GetRequiredService>() + .InitConfig("plugins.json", true) + .InitDefaults()); + + + services.AddHttpClient() + .AddNuGetPluginsFetcher(); // You should implement this in NuGetPluginsFetcher class as an extension method + + return services; + } +} \ No newline at end of file diff --git a/src/YumeChan.Core/Services/DiscordBotTokenProvider.cs b/src/YumeChan.Core/Services/DiscordBotTokenProvider.cs new file mode 100644 index 0000000..944293a --- /dev/null +++ b/src/YumeChan.Core/Services/DiscordBotTokenProvider.cs @@ -0,0 +1,77 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; +using YumeChan.Core.Config; + +namespace YumeChan.Core.Services; + +/// +/// Provides the Discord bot token. +/// +internal sealed class DiscordBotTokenProvider +{ + private readonly ICoreProperties _coreProperties; + private readonly ILogger _logger; + + public DiscordBotTokenProvider(ICoreProperties coreProperties, ILogger logger) + { + _coreProperties = coreProperties; + _logger = logger; + } + + /// + /// Gets the Discord bot token from either the , or from the environment variables. + /// + /// The Discord bot token. + /// Thrown if no bot token was supplied. + public string GetBotToken() + { + string? token = _coreProperties.BotToken; + + if (!string.IsNullOrWhiteSpace(token)) + { + return token; + } + + string envVarName = $"{_coreProperties.AppInternalName}.Token"; + + if (TryBotTokenFromEnvironment(envVarName, out token, out EnvironmentVariableTarget target)) + { + _logger.LogInformation("Bot Token was read from {target} Environment Variable \"{envVar}\", instead of \"coreproperties.json\" Config File", target, envVarName); + return token; + } + + ApplicationException e = new("No Bot Token supplied."); + _logger.LogCritical(e, "No Bot Token was found in \"coreconfig.json\" Config File, and Environment Variables \"{envVar}\" from relevant targets are empty. Please set a Bot Token before launching the Bot", envVarName); + + throw e; + } + + private static bool TryBotTokenFromEnvironment(string envVarName, [NotNullWhen(true)] out string? token, out EnvironmentVariableTarget foundFromTarget) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + foreach (EnvironmentVariableTarget target in typeof(EnvironmentVariableTarget).GetEnumValues()) + { + token = Environment.GetEnvironmentVariable(envVarName, target); + + if (token is not null) + { + foundFromTarget = target; + + return true; + } + } + + token = null; + foundFromTarget = default; + + return false; + } + + token = Environment.GetEnvironmentVariable(envVarName); + foundFromTarget = EnvironmentVariableTarget.Process; + + return token is not null; + } +} \ No newline at end of file diff --git a/src/YumeChan.Core/YumeChan.Core.csproj b/src/YumeChan.Core/YumeChan.Core.csproj index 89c1393..81a33e1 100644 --- a/src/YumeChan.Core/YumeChan.Core.csproj +++ b/src/YumeChan.Core/YumeChan.Core.csproj @@ -13,6 +13,7 @@ false preview enable + enable diff --git a/src/YumeChan.Core/YumeCore.cs b/src/YumeChan.Core/YumeCore.cs index fc6bca7..714b996 100644 --- a/src/YumeChan.Core/YumeCore.cs +++ b/src/YumeChan.Core/YumeCore.cs @@ -24,7 +24,7 @@ public enum YumeCoreState public sealed class YumeCore { - public static YumeCore Instance => _instance ??= new(); + public static YumeCore Instance => _instance!; private static YumeCore? _instance; public YumeCoreState CoreState { get; private set; } @@ -42,50 +42,33 @@ public sealed class YumeCore public ICoreProperties CoreProperties { get; private set; } - public YumeCore() { } + public YumeCore(IUnityContainer services) + { + Services = services; + + Logger = Services.Resolve>(); + ConfigProvider = Services.Resolve>(); + + CoreProperties = ConfigProvider.InitConfig("coreconfig.json", true).InitDefaults(); + CoreProperties.Path_Core ??= Directory.GetCurrentDirectory(); + CoreProperties.Path_Plugins ??= Path.Combine(CoreProperties.Path_Core, "Plugins"); + CoreProperties.Path_Config ??= Path.Combine(CoreProperties.Path_Core, "Config"); + + Client = Services.Resolve(); + CommandHandler = Services.Resolve(); + CommandHandler.Config = CoreProperties; + + LavalinkHandler = Services.Resolve(); + LavalinkHandler.Config = CoreProperties.LavalinkProperties; + + _instance = this; + } ~YumeCore() { StopBotAsync().Wait(); } - public IUnityContainer ConfigureContainer(IUnityContainer container) => container - .RegisterFactory(uc => new DiscordClient(new() - { - Intents = DiscordIntents.All, - TokenType = TokenType.Bot, - Token = GetBotToken(), - LoggerFactory = uc.Resolve(), - MinimumLogLevel = LogLevel.Information - }), - FactoryLifetime.Singleton - ) - - .RegisterFactory(static uc => new PluginsLoader(uc.Resolve().Path_Plugins), FactoryLifetime.Singleton) - - .RegisterInstance(PluginLifetimeListener.Instance) - .RegisterSingleton() - .RegisterSingleton() - .RegisterSingleton() - .RegisterSingleton(typeof(IMongoDatabaseProvider<>), typeof(UnifiedDatabaseProvider<>)) - .RegisterSingleton(typeof(IPostgresDatabaseProvider<>), typeof(UnifiedDatabaseProvider<>)) - .RegisterSingleton(typeof(IInterfaceConfigProvider<>), typeof(InterfaceConfigProvider<>)) - .RegisterSingleton(typeof(IJsonConfigProvider<>), typeof(JsonConfigProvider<>)) - - .RegisterFactory( - static uc => uc.Resolve>().InitConfig("coreconfig.json", true).InitDefaults(), - FactoryLifetime.Singleton) - - .RegisterFactory( - static uc => uc.Resolve>().InitConfig("plugins.json", true).InitDefaults(), - FactoryLifetime.Singleton) - - .AddServices(new ServiceCollection() - .AddHttpClient() - .AddNuGetPluginsFetcher() - ); - - public async Task StartBotAsync() { if (Services is null) @@ -93,8 +76,6 @@ public async Task StartBotAsync() throw new InvalidOperationException("Service Provider has not been defined.", new ArgumentNullException(nameof(Services))); } - ResolveCoreComponents(); - Logger.LogInformation("YumeCore v{version}", CoreVersion); CoreState = YumeCoreState.Starting; @@ -136,75 +117,4 @@ public async Task ReloadCommandsAsync() await CommandHandler.RegisterCommandsAsync(); CoreState = YumeCoreState.Online; } - - - private string GetBotToken() - { - string? token = CoreProperties.BotToken; - - if (!string.IsNullOrWhiteSpace(token)) - { - return token; - } - - string envVarName = $"{CoreProperties.AppInternalName}.Token"; - - if (TryBotTokenFromEnvironment(envVarName, out token, out EnvironmentVariableTarget target)) - { - Logger.LogInformation("Bot Token was read from {target} Environment Variable \"{envVar}\", instead of \"coreproperties.json\" Config File", target, envVarName); - return token; - } - - ApplicationException e = new("No Bot Token supplied."); - Logger.LogCritical(e, "No Bot Token was found in \"coreconfig.json\" Config File, and Environment Variables \"{envVar}\" from relevant targets are empty. Please set a Bot Token before launching the Bot", envVarName); - - throw e; - } - - private static bool TryBotTokenFromEnvironment(string envVarName, [NotNullWhen(true)] out string? token, out EnvironmentVariableTarget foundFromTarget) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - foreach (EnvironmentVariableTarget target in typeof(EnvironmentVariableTarget).GetEnumValues()) - { - token = Environment.GetEnvironmentVariable(envVarName, target); - - if (token is not null) - { - foundFromTarget = target; - - return true; - } - } - - token = null; - foundFromTarget = default; - - return false; - } - - token = Environment.GetEnvironmentVariable(envVarName); - foundFromTarget = EnvironmentVariableTarget.Process; - - return token is not null; - } - - [SuppressMessage("ReSharper", "NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract")] - private void ResolveCoreComponents() - { - Logger ??= Services.Resolve>(); - ConfigProvider ??= Services.Resolve>(); - - CoreProperties = ConfigProvider.InitConfig("coreconfig.json", true).InitDefaults(); - CoreProperties.Path_Core ??= Directory.GetCurrentDirectory(); - CoreProperties.Path_Plugins ??= Path.Combine(CoreProperties.Path_Core, "Plugins"); - CoreProperties.Path_Config ??= Path.Combine(CoreProperties.Path_Core, "Config"); - - Client ??= Services.Resolve(); - CommandHandler ??= Services.Resolve(); - CommandHandler.Config ??= CoreProperties; - - LavalinkHandler ??= Services.Resolve(); - LavalinkHandler.Config ??= CoreProperties.LavalinkProperties; - } } \ No newline at end of file diff --git a/src/YumeChan.NetRunner/Program.cs b/src/YumeChan.NetRunner/Program.cs index 175be75..56f08bf 100644 --- a/src/YumeChan.NetRunner/Program.cs +++ b/src/YumeChan.NetRunner/Program.cs @@ -20,6 +20,7 @@ public static class Program public static async Task Main(string[] args) { string informationalVersion = Assembly.GetEntryAssembly()?.GetCustomAttribute()?.InformationalVersion; + _container.RegisterInstance(new NetRunnerContext(RunnerType.Console, typeof(Program).Assembly.GetName().Name, informationalVersion)); Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() @@ -28,15 +29,14 @@ public static async Task Main(string[] args) .Enrich.FromLogContext() .WriteTo.Console() .CreateLogger(); + + using IHost host = CreateHostBuilder(args).Build(); + IServiceProvider services = host.Services; - - IHost host = CreateHostBuilder(args).Build(); - - YumeCore.Instance.Services = _container; - _container.RegisterInstance(new NetRunnerContext(RunnerType.Console, typeof(Program).Assembly.GetName().Name, informationalVersion)); + YumeCore yumeCore = services.GetRequiredService(); await Task.WhenAll( - YumeCore.Instance.StartBotAsync(), + yumeCore.StartBotAsync(), host.RunAsync() ); @@ -49,10 +49,6 @@ public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaul .ConfigureContainer((_, container) => { _container = container; // This assignment is necessary, as configuration only affects the child container. - container.AddExtension(new LoggingExtension(new SerilogLoggerFactory())); - container.AddServices(new ServiceCollection().AddLogging(x => x.AddSerilog())); - - YumeCore.Instance.ConfigureContainer(container); }); } diff --git a/src/YumeChan.NetRunner/Startup.cs b/src/YumeChan.NetRunner/Startup.cs index aa776e0..f3ff5fb 100644 --- a/src/YumeChan.NetRunner/Startup.cs +++ b/src/YumeChan.NetRunner/Startup.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Serilog; using YumeChan.Core.Config; using YumeChan.NetRunner.Infrastructure.Blazor; using YumeChan.NetRunner.Plugins.Infrastructure; @@ -34,13 +35,15 @@ public Startup(IConfiguration configuration) // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { + services.AddYumeCoreServices(); + services.AddControllers(builder => { builder.ConfigurePluginNameRoutingToken(); builder.Conventions.Add(new PluginApiRoutingConvention()); } ); - + services.AddApiPluginSupport(); services.AddApiPluginsSwagger(); services.AddPluginDocsSupport(); @@ -73,13 +76,10 @@ public void ConfigureServices(IServiceCollection services) services.AddLogging(x => { x.ClearProviders(); + x.AddSerilog(); }); - - services.AddSingleton(); - services.AddSingleton(YumeCore.Instance); - services.AddScoped(); }