diff --git a/DisCatSharp.Hosting/BaseHostedService.cs b/DisCatSharp.Hosting/BaseHostedService.cs index 78cdc2de3..88e6f4760 100644 --- a/DisCatSharp.Hosting/BaseHostedService.cs +++ b/DisCatSharp.Hosting/BaseHostedService.cs @@ -1,182 +1,182 @@ // This file is part of the DisCatSharp project, a fork of DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace DisCatSharp.Hosting { /// /// Contains the common logic between having a or /// as a Hosted Service /// public abstract class BaseHostedService : BackgroundService { protected readonly ILogger Logger; protected readonly IHostApplicationLifetime ApplicationLifetime; protected readonly IConfiguration Configuration; protected readonly IServiceProvider ServiceProvider; protected readonly string BotSection; internal BaseHostedService(IConfiguration config, ILogger logger, IServiceProvider serviceProvider, IHostApplicationLifetime applicationLifetime, string configBotSection = DisCatSharp.Configuration.ConfigurationExtensions.DefaultRootLib) { this.Configuration = config; this.Logger = logger; this.ApplicationLifetime = applicationLifetime; this.ServiceProvider = serviceProvider; this.BotSection = configBotSection; } /// /// When the bot(s) fail to start, this method will be invoked. (Default behavior is to shutdown) /// /// The exception/reason for not starting protected virtual void OnInitializationError(Exception ex) => this.ApplicationLifetime.StopApplication(); /// /// Connect your client(s) to Discord /// /// Task protected abstract Task ConnectAsync(); /// /// Default behavior is to dynamically load extensions by using and /// /// /// Client to add extension method(s) to /// Task protected virtual Task InitializeExtensions(DiscordClient client) { - var typeMap = this.Configuration.FindImplementedExtensions(this.BotSection); + var typeMap = this.Configuration.FindImplementedExtensions(this.BotSection); this.Logger.LogDebug($"Found the following config types: {string.Join("\n\t", typeMap.Keys)}"); foreach (var typePair in typeMap) try { /* If section is null --> utilize the default constructor This means the extension was explicitly added in the 'Using' array, but user did not wish to override any value(s) in the extension's config */ var configInstance = typePair.Value.Section.HasValue ? typePair.Value.Section.Value.ExtractConfig(() => ActivatorUtilities.CreateInstance(this.ServiceProvider, typePair.Value.ConfigType)) : ActivatorUtilities.CreateInstance(this.ServiceProvider, typePair.Value.ConfigType); /* Explanation for bindings Internal Constructors --> NonPublic Public Constructors --> Public Constructors --> Instance */ var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; var ctors = typePair.Value.ImplementationType.GetConstructors(flags); var instance = ctors.Any(x => x.GetParameters().Length == 1 && x.GetParameters().First().ParameterType == typePair.Value.ConfigType) ? Activator.CreateInstance(typePair.Value.ImplementationType, flags, null, new[] { configInstance }, null) : Activator.CreateInstance(typePair.Value.ImplementationType, true); /* Certain extensions do not require a configuration argument Those who do -- pass config instance in, Those who don't -- simply instantiate ActivatorUtilities requires a public constructor, anything with internal breaks */ if (instance == null) { this.Logger.LogError($"Unable to instantiate '{typePair.Value.ImplementationType.Name}'"); continue; } // Add an easy reference to our extensions for later use client.AddExtension((BaseExtension)instance); } catch (Exception ex) { this.Logger.LogError($"Unable to register '{typePair.Value.ImplementationType.Name}': \n\t{ex.Message}"); this.OnInitializationError(ex); } return Task.CompletedTask; } /// /// Runs just prior to . Should initialize your /// or here /// /// protected abstract Task PreConnectAsync(); /// /// Runs immediately after . /// Recommended to initialize your extensions here /// /// Task protected abstract Task PostConnectAsync(); protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { await this.PreConnectAsync(); await this.ConnectAsync(); await this.PostConnectAsync(); } catch (Exception ex) { /* * Anything before DOTNET 6 will * fail silently despite throwing an exception in this method * So to overcome this obstacle we need to log what happens and * manually exit */ this.Logger.LogError(($"Was unable to start {this.GetType().Name} Bot as a Hosted Service")); // Power given to developer for handling exception this.OnInitializationError(ex); } // Wait indefinitely -- but use stopping token so we can properly cancel if needed await Task.Delay(-1, stoppingToken); } } }