[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);
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<LoadedPlugin> _loaded = [];
private IEnumerable<IPlugin> Plugins => _loaded.Select(p => p.instance);
public IEnumerable<Assembly> Assemblies => _loaded.Select(p => p.descriptor.Assembly);
private static readonly string DllPath = Path.Combine(PathRoot, $"v{ApiVersion}");
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;
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<IPlugin>()))
.Cast<IPluginCatalog>()
@ -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<ILogger<PluginLoader>>();
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));
}
}

View file

@ -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<ICronTask>();
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<ICronTask>();
foreach (var task in tasks)
{

View file

@ -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<StreamingHub>("/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)