| | | 1 | | using System.Runtime.Loader; |
| | | 2 | | using Microsoft.Extensions.Configuration; |
| | | 3 | | using Microsoft.Extensions.DependencyInjection; |
| | | 4 | | using Microsoft.Extensions.Hosting; |
| | | 5 | | using Microsoft.Extensions.Logging; |
| | | 6 | | |
| | | 7 | | namespace NLightning.Daemon.Services; |
| | | 8 | | |
| | | 9 | | using Models; |
| | | 10 | | using Plugins; |
| | | 11 | | |
| | | 12 | | public class PluginLoaderService : IHostedService |
| | | 13 | | { |
| | | 14 | | private readonly IServiceProvider _services; |
| | | 15 | | private readonly IConfiguration _config; |
| | | 16 | | private readonly ILogger<PluginLoaderService> _logger; |
| | 0 | 17 | | private readonly List<(IDaemonPlugin Plugin, AssemblyLoadContext Alc)> _plugins = new(); |
| | | 18 | | |
| | 0 | 19 | | public PluginLoaderService(IServiceProvider services, IConfiguration config, ILogger<PluginLoaderService> logger) |
| | | 20 | | { |
| | 0 | 21 | | _services = services; |
| | 0 | 22 | | _config = config; |
| | 0 | 23 | | _logger = logger; |
| | 0 | 24 | | } |
| | | 25 | | |
| | | 26 | | public async Task StartAsync(CancellationToken cancellationToken) |
| | | 27 | | { |
| | 0 | 28 | | var entries = _config.GetSection("Plugins").Get<List<PluginEntry>>() ?? []; |
| | 0 | 29 | | foreach (var entry in entries) |
| | | 30 | | { |
| | | 31 | | try |
| | | 32 | | { |
| | 0 | 33 | | var alc = new AssemblyLoadContext(Path.GetFileNameWithoutExtension(entry.AssemblyPath), |
| | 0 | 34 | | isCollectible: true); |
| | 0 | 35 | | await using var stream = File.OpenRead(entry.AssemblyPath); |
| | 0 | 36 | | var asm = alc.LoadFromStream(stream); |
| | | 37 | | |
| | 0 | 38 | | var pluginType = string.IsNullOrWhiteSpace(entry.TypeName) |
| | 0 | 39 | | ? asm.ExportedTypes.First(t => typeof(IDaemonPlugin).IsAssignableFrom(t) && |
| | 0 | 40 | | !t.IsAbstract) |
| | 0 | 41 | | : asm.GetType(entry.TypeName!, throwOnError: true)!; |
| | | 42 | | |
| | 0 | 43 | | var plugin = (IDaemonPlugin)ActivatorUtilities.CreateInstance( |
| | 0 | 44 | | _services, pluginType); |
| | | 45 | | |
| | 0 | 46 | | var context = _services.GetRequiredService<IDaemonContext>(); |
| | 0 | 47 | | await plugin.StartAsync(context, cancellationToken); |
| | | 48 | | |
| | 0 | 49 | | _plugins.Add((plugin, alc)); |
| | 0 | 50 | | _logger.LogInformation("Loaded plugin {Plugin}", pluginType.FullName); |
| | 0 | 51 | | } |
| | 0 | 52 | | catch (Exception ex) |
| | | 53 | | { |
| | 0 | 54 | | _logger.LogError(ex, "Failed to load plugin from {Path}", entry.AssemblyPath); |
| | 0 | 55 | | } |
| | 0 | 56 | | } |
| | 0 | 57 | | } |
| | | 58 | | |
| | | 59 | | public async Task StopAsync(CancellationToken cancellationToken) |
| | | 60 | | { |
| | 0 | 61 | | foreach (var (plugin, alc) in _plugins) |
| | | 62 | | { |
| | 0 | 63 | | try { await plugin.StopAsync(cancellationToken); } |
| | 0 | 64 | | catch (Exception ex) { _logger.LogWarning(ex, "Error stopping plugin {Name}", plugin.Name); } |
| | | 65 | | |
| | 0 | 66 | | await plugin.DisposeAsync(); |
| | 0 | 67 | | alc.Unload(); |
| | 0 | 68 | | } |
| | | 69 | | |
| | 0 | 70 | | _plugins.Clear(); |
| | 0 | 71 | | } |
| | | 72 | | } |