From a73587d142b46396819acb6b7659cea4034f0041 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Sat, 13 Jul 2024 23:28:41 +0200 Subject: [PATCH] [backend/plugins] Allow plugins to instantiate cron tasks (ISH-422) --- .../Core/Helpers/PluginLoader.cs | 36 +++++++++---------- .../Core/Services/CronService.cs | 11 +++--- Iceshrimp.Backend/Startup.cs | 11 +++--- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Iceshrimp.Backend/Core/Helpers/PluginLoader.cs b/Iceshrimp.Backend/Core/Helpers/PluginLoader.cs index 80177bff..477236c0 100644 --- a/Iceshrimp.Backend/Core/Helpers/PluginLoader.cs +++ b/Iceshrimp.Backend/Core/Helpers/PluginLoader.cs @@ -7,27 +7,25 @@ namespace Iceshrimp.Backend.Core.Helpers; using LoadedPlugin = (Plugin descriptor, IPlugin instance); -public class PluginLoader +public abstract class PluginLoader { // Increment whenever a breaking plugin API change is made private const int ApiVersion = 1; - public PluginLoader() - { - var path = Environment.GetEnvironmentVariable("ICESHRIMP_PLUGIN_DIR"); - path ??= Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "plugins"); - _path = Path.Combine(path, $"v{ApiVersion}"); - } + private static readonly string PathRoot = Environment.GetEnvironmentVariable("ICESHRIMP_PLUGIN_DIR") ?? + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, + "plugins"); - private readonly string _path; - private List _loaded = []; - private IEnumerable Plugins => _loaded.Select(p => p.instance); - public IEnumerable Assemblies => _loaded.Select(p => p.descriptor.Assembly); + private static readonly string DllPath = Path.Combine(PathRoot, $"v{ApiVersion}"); - public async Task LoadPlugins() + private static List _loaded = []; + private static IEnumerable Plugins => _loaded.Select(p => p.instance); + public static IEnumerable Assemblies => _loaded.Select(p => p.descriptor.Assembly); + + public static async Task LoadPlugins() { - if (!Directory.Exists(_path)) return; - var dlls = Directory.EnumerateFiles(_path, "*.dll").ToList(); + if (!Directory.Exists(DllPath)) return; + var dlls = Directory.EnumerateFiles(DllPath, "*.dll").ToList(); var catalogs = dlls .Select(p => new AssemblyPluginCatalog(p, type => type.Implements())) .Cast() @@ -44,28 +42,28 @@ public class PluginLoader await Plugins.Select(i => i.Initialize()).AwaitAllNoConcurrencyAsync(); } - public void RunBuilderHooks(WebApplicationBuilder builder) + public static void RunBuilderHooks(WebApplicationBuilder builder) { foreach (var plugin in Plugins) plugin.BuilderHook(builder); } - public void RunAppHooks(WebApplication app) + public static void RunAppHooks(WebApplication app) { foreach (var plugin in Plugins) plugin.AppHook(app); } - public void PrintPluginInformation(WebApplication app) + public static void PrintPluginInformation(WebApplication app) { var logger = app.Services.GetRequiredService>(); if (_loaded.Count == 0) { - logger.LogInformation("Found {count} plugins in {dllPath}.", _loaded.Count, _path); + logger.LogInformation("Found {count} plugins in {dllPath}.", _loaded.Count, DllPath); return; } var plugins = _loaded.Select(plugin => $"{plugin.instance.Name} v{plugin.instance.Version} " + $"({Path.GetFileName(plugin.descriptor.Assembly.Location)})"); - logger.LogInformation("Loaded {count} plugins from {dllPath}: \n* {files}", _loaded.Count, _path, + logger.LogInformation("Loaded {count} plugins from {dllPath}: \n* {files}", _loaded.Count, DllPath, string.Join("\n* ", plugins)); } } diff --git a/Iceshrimp.Backend/Core/Services/CronService.cs b/Iceshrimp.Backend/Core/Services/CronService.cs index a1e69010..46bec479 100644 --- a/Iceshrimp.Backend/Core/Services/CronService.cs +++ b/Iceshrimp.Backend/Core/Services/CronService.cs @@ -1,3 +1,4 @@ +using System.Reflection; using Iceshrimp.Backend.Core.Helpers; namespace Iceshrimp.Backend.Core.Services; @@ -6,10 +7,12 @@ public class CronService(IServiceScopeFactory serviceScopeFactory) : BackgroundS { protected override Task ExecuteAsync(CancellationToken token) { - var tasks = AssemblyHelpers.GetImplementationsOfInterface(typeof(ICronTask)) - .Select(p => Activator.CreateInstance(p) as ICronTask) - .Where(p => p != null) - .Cast(); + var tasks = PluginLoader + .Assemblies.Prepend(Assembly.GetExecutingAssembly()) + .SelectMany(assembly => AssemblyHelpers.GetImplementationsOfInterface(typeof(ICronTask), assembly)) + .Select(p => Activator.CreateInstance(p) as ICronTask) + .Where(p => p != null) + .Cast(); foreach (var task in tasks) { diff --git a/Iceshrimp.Backend/Startup.cs b/Iceshrimp.Backend/Startup.cs index f8d0393f..1bc488b0 100644 --- a/Iceshrimp.Backend/Startup.cs +++ b/Iceshrimp.Backend/Startup.cs @@ -10,8 +10,7 @@ var builder = WebApplication.CreateBuilder(args); builder.Configuration.Sources.Clear(); builder.Configuration.AddCustomConfiguration(); -var pluginLoader = new PluginLoader(); -await pluginLoader.LoadPlugins(); +await PluginLoader.LoadPlugins(); builder.Services.AddControllers() .AddNewtonsoftJson() //TODO: remove once dotNetRdf switches to System.Text.Json (or we switch to LinkedData.NET) @@ -20,7 +19,7 @@ builder.Services.AddControllers() .AddModelBindingProviders() .AddValueProviderFactories() .AddApiBehaviorOptions() - .AddPlugins(pluginLoader.Assemblies); + .AddPlugins(PluginLoader.Assemblies); builder.Services.AddSwaggerGenWithOptions(); builder.Services.AddLogging(logging => logging.AddCustomConsoleFormatter()); @@ -46,7 +45,7 @@ builder.Services.ConfigureServices(builder.Configuration); builder.WebHost.ConfigureKestrel(builder.Configuration); builder.WebHost.UseStaticWebAssets(); -pluginLoader.RunBuilderHooks(builder); +PluginLoader.RunBuilderHooks(builder); var app = builder.Build(); var config = await app.Initialize(args); @@ -76,8 +75,8 @@ app.MapHub("/hubs/streaming"); app.MapRazorPages(); app.MapFrontendRoutes("/Shared/FrontendSPA"); -pluginLoader.RunAppHooks(app); -pluginLoader.PrintPluginInformation(app); +PluginLoader.RunAppHooks(app); +PluginLoader.PrintPluginInformation(app); // If running under IIS, this collection is read only if (!app.Urls.IsReadOnly)