[backend/plugins] Allow plugins to instantiate cron tasks (ISH-422)

This commit is contained in:
Laura Hausmann 2024-07-13 23:28:41 +02:00
parent c309fb00c9
commit a73587d142
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
3 changed files with 29 additions and 29 deletions

View file

@ -7,27 +7,25 @@ namespace Iceshrimp.Backend.Core.Helpers;
using LoadedPlugin = (Plugin descriptor, IPlugin instance); using LoadedPlugin = (Plugin descriptor, IPlugin instance);
public class PluginLoader public abstract class PluginLoader
{ {
// Increment whenever a breaking plugin API change is made // Increment whenever a breaking plugin API change is made
private const int ApiVersion = 1; private const int ApiVersion = 1;
public PluginLoader() private static readonly string PathRoot = Environment.GetEnvironmentVariable("ICESHRIMP_PLUGIN_DIR") ??
{ Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
var path = Environment.GetEnvironmentVariable("ICESHRIMP_PLUGIN_DIR"); "plugins");
path ??= Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "plugins");
_path = Path.Combine(path, $"v{ApiVersion}");
}
private readonly string _path; private static readonly string DllPath = Path.Combine(PathRoot, $"v{ApiVersion}");
private List<LoadedPlugin> _loaded = [];
private IEnumerable<IPlugin> Plugins => _loaded.Select(p => p.instance);
public IEnumerable<Assembly> Assemblies => _loaded.Select(p => p.descriptor.Assembly);
public async Task LoadPlugins() private static List<LoadedPlugin> _loaded = [];
private static IEnumerable<IPlugin> Plugins => _loaded.Select(p => p.instance);
public static IEnumerable<Assembly> Assemblies => _loaded.Select(p => p.descriptor.Assembly);
public static async Task LoadPlugins()
{ {
if (!Directory.Exists(_path)) return; if (!Directory.Exists(DllPath)) return;
var dlls = Directory.EnumerateFiles(_path, "*.dll").ToList(); var dlls = Directory.EnumerateFiles(DllPath, "*.dll").ToList();
var catalogs = dlls var catalogs = dlls
.Select(p => new AssemblyPluginCatalog(p, type => type.Implements<IPlugin>())) .Select(p => new AssemblyPluginCatalog(p, type => type.Implements<IPlugin>()))
.Cast<IPluginCatalog>() .Cast<IPluginCatalog>()
@ -44,28 +42,28 @@ public class PluginLoader
await Plugins.Select(i => i.Initialize()).AwaitAllNoConcurrencyAsync(); 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); 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); foreach (var plugin in Plugins) plugin.AppHook(app);
} }
public void PrintPluginInformation(WebApplication app) public static void PrintPluginInformation(WebApplication app)
{ {
var logger = app.Services.GetRequiredService<ILogger<PluginLoader>>(); var logger = app.Services.GetRequiredService<ILogger<PluginLoader>>();
if (_loaded.Count == 0) 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; return;
} }
var plugins = _loaded.Select(plugin => $"{plugin.instance.Name} v{plugin.instance.Version} " + var plugins = _loaded.Select(plugin => $"{plugin.instance.Name} v{plugin.instance.Version} " +
$"({Path.GetFileName(plugin.descriptor.Assembly.Location)})"); $"({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)); string.Join("\n* ", plugins));
} }
} }

View file

@ -1,3 +1,4 @@
using System.Reflection;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
@ -6,10 +7,12 @@ public class CronService(IServiceScopeFactory serviceScopeFactory) : BackgroundS
{ {
protected override Task ExecuteAsync(CancellationToken token) protected override Task ExecuteAsync(CancellationToken token)
{ {
var tasks = AssemblyHelpers.GetImplementationsOfInterface(typeof(ICronTask)) var tasks = PluginLoader
.Select(p => Activator.CreateInstance(p) as ICronTask) .Assemblies.Prepend(Assembly.GetExecutingAssembly())
.Where(p => p != null) .SelectMany(assembly => AssemblyHelpers.GetImplementationsOfInterface(typeof(ICronTask), assembly))
.Cast<ICronTask>(); .Select(p => Activator.CreateInstance(p) as ICronTask)
.Where(p => p != null)
.Cast<ICronTask>();
foreach (var task in tasks) foreach (var task in tasks)
{ {

View file

@ -10,8 +10,7 @@ var builder = WebApplication.CreateBuilder(args);
builder.Configuration.Sources.Clear(); builder.Configuration.Sources.Clear();
builder.Configuration.AddCustomConfiguration(); builder.Configuration.AddCustomConfiguration();
var pluginLoader = new PluginLoader(); await PluginLoader.LoadPlugins();
await pluginLoader.LoadPlugins();
builder.Services.AddControllers() builder.Services.AddControllers()
.AddNewtonsoftJson() //TODO: remove once dotNetRdf switches to System.Text.Json (or we switch to LinkedData.NET) .AddNewtonsoftJson() //TODO: remove once dotNetRdf switches to System.Text.Json (or we switch to LinkedData.NET)
@ -20,7 +19,7 @@ builder.Services.AddControllers()
.AddModelBindingProviders() .AddModelBindingProviders()
.AddValueProviderFactories() .AddValueProviderFactories()
.AddApiBehaviorOptions() .AddApiBehaviorOptions()
.AddPlugins(pluginLoader.Assemblies); .AddPlugins(PluginLoader.Assemblies);
builder.Services.AddSwaggerGenWithOptions(); builder.Services.AddSwaggerGenWithOptions();
builder.Services.AddLogging(logging => logging.AddCustomConsoleFormatter()); builder.Services.AddLogging(logging => logging.AddCustomConsoleFormatter());
@ -46,7 +45,7 @@ builder.Services.ConfigureServices(builder.Configuration);
builder.WebHost.ConfigureKestrel(builder.Configuration); builder.WebHost.ConfigureKestrel(builder.Configuration);
builder.WebHost.UseStaticWebAssets(); builder.WebHost.UseStaticWebAssets();
pluginLoader.RunBuilderHooks(builder); PluginLoader.RunBuilderHooks(builder);
var app = builder.Build(); var app = builder.Build();
var config = await app.Initialize(args); var config = await app.Initialize(args);
@ -76,8 +75,8 @@ app.MapHub<StreamingHub>("/hubs/streaming");
app.MapRazorPages(); app.MapRazorPages();
app.MapFrontendRoutes("/Shared/FrontendSPA"); app.MapFrontendRoutes("/Shared/FrontendSPA");
pluginLoader.RunAppHooks(app); PluginLoader.RunAppHooks(app);
pluginLoader.PrintPluginInformation(app); PluginLoader.PrintPluginInformation(app);
// If running under IIS, this collection is read only // If running under IIS, this collection is read only
if (!app.Urls.IsReadOnly) if (!app.Urls.IsReadOnly)