| using System.Reflection; |
| using Microsoft.Extensions.Logging; |
| using SilkroadBot.Plugins.SDK.Attributes; |
| using SilkroadBot.Plugins.SDK.Interfaces; |
|
|
| namespace SilkroadBot.Core.Plugins; |
|
|
| |
| |
| |
| |
| public class PluginManager : IDisposable |
| { |
| private readonly ILogger<PluginManager> _logger; |
| private readonly IServiceProvider _serviceProvider; |
| private readonly Dictionary<string, PluginInstance> _plugins = new(); |
| private readonly string _pluginDirectory; |
|
|
| public IReadOnlyDictionary<string, PluginInstance> Plugins => _plugins; |
| |
| public event Action<PluginInstance>? PluginLoaded; |
| public event Action<PluginInstance>? PluginUnloaded; |
| public event Action<string, Exception>? PluginError; |
|
|
| public PluginManager( |
| string pluginDirectory, |
| IServiceProvider serviceProvider, |
| ILogger<PluginManager> logger) |
| { |
| _pluginDirectory = pluginDirectory; |
| _serviceProvider = serviceProvider; |
| _logger = logger; |
| |
| if (!Directory.Exists(_pluginDirectory)) |
| Directory.CreateDirectory(_pluginDirectory); |
| } |
|
|
| |
| |
| |
| public async Task DiscoverAndLoadAsync(IPluginContext context) |
| { |
| _logger.LogInformation("Scanning for plugins in: {Dir}", _pluginDirectory); |
| |
| var dllFiles = Directory.GetFiles(_pluginDirectory, "*.dll", SearchOption.AllDirectories); |
| |
| foreach (var dll in dllFiles) |
| { |
| try |
| { |
| await LoadPluginFromAssemblyAsync(dll, context); |
| } |
| catch (Exception ex) |
| { |
| _logger.LogError(ex, "Failed to load plugin from {Dll}", dll); |
| PluginError?.Invoke(dll, ex); |
| } |
| } |
| |
| _logger.LogInformation("Loaded {Count} plugins", _plugins.Count); |
| } |
|
|
| |
| |
| |
| public async Task<PluginInstance?> LoadPluginFromAssemblyAsync(string dllPath, IPluginContext context) |
| { |
| var assembly = Assembly.LoadFrom(dllPath); |
| var pluginTypes = assembly.GetTypes() |
| .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface) |
| .ToList(); |
|
|
| PluginInstance? lastLoaded = null; |
| |
| foreach (var type in pluginTypes) |
| { |
| var attr = type.GetCustomAttribute<PluginAttribute>(); |
| var plugin = (IPlugin)Activator.CreateInstance(type)!; |
| |
| var instance = new PluginInstance |
| { |
| Plugin = plugin, |
| Assembly = assembly, |
| FilePath = dllPath, |
| Metadata = attr != null |
| ? new PluginMetadata(attr.Id, attr.Name, attr.Description) |
| : new PluginMetadata(plugin.Id, plugin.Name, plugin.Description) |
| }; |
|
|
| _logger.LogInformation("Loading plugin: {Name} v{Version} by {Author}", |
| plugin.Name, plugin.Version, plugin.Author); |
|
|
| await plugin.InitializeAsync(context); |
| _plugins[plugin.Id] = instance; |
| PluginLoaded?.Invoke(instance); |
| lastLoaded = instance; |
| } |
|
|
| return lastLoaded; |
| } |
|
|
| |
| |
| |
| public async Task StartPluginAsync(string pluginId, CancellationToken ct = default) |
| { |
| if (!_plugins.TryGetValue(pluginId, out var instance)) |
| throw new KeyNotFoundException($"Plugin '{pluginId}' not found"); |
|
|
| await instance.Plugin.StartAsync(ct); |
| instance.IsRunning = true; |
| _logger.LogInformation("Started plugin: {Name}", instance.Plugin.Name); |
| } |
|
|
| |
| |
| |
| public async Task StopPluginAsync(string pluginId) |
| { |
| if (!_plugins.TryGetValue(pluginId, out var instance)) |
| throw new KeyNotFoundException($"Plugin '{pluginId}' not found"); |
|
|
| await instance.Plugin.StopAsync(); |
| instance.IsRunning = false; |
| _logger.LogInformation("Stopped plugin: {Name}", instance.Plugin.Name); |
| } |
|
|
| |
| |
| |
| public async Task StartAllAsync(CancellationToken ct = default) |
| { |
| foreach (var kv in _plugins) |
| { |
| try { await StartPluginAsync(kv.Key, ct); } |
| catch (Exception ex) |
| { |
| _logger.LogError(ex, "Failed to start plugin {Id}", kv.Key); |
| PluginError?.Invoke(kv.Key, ex); |
| } |
| } |
| } |
|
|
| |
| |
| |
| public async Task StopAllAsync() |
| { |
| foreach (var kv in _plugins.Where(p => p.Value.IsRunning)) |
| { |
| try { await StopPluginAsync(kv.Key); } |
| catch (Exception ex) |
| { |
| _logger.LogError(ex, "Failed to stop plugin {Id}", kv.Key); |
| } |
| } |
| } |
|
|
| |
| |
| |
| public async Task UnloadPluginAsync(string pluginId) |
| { |
| if (!_plugins.TryGetValue(pluginId, out var instance)) |
| return; |
|
|
| if (instance.IsRunning) |
| await StopPluginAsync(pluginId); |
|
|
| instance.Plugin.Dispose(); |
| _plugins.Remove(pluginId); |
| PluginUnloaded?.Invoke(instance); |
| _logger.LogInformation("Unloaded plugin: {Name}", instance.Metadata.Name); |
| } |
|
|
| public void Dispose() |
| { |
| foreach (var kv in _plugins) |
| kv.Value.Plugin.Dispose(); |
| _plugins.Clear(); |
| } |
| } |
|
|
| |
| |
| |
| public class PluginInstance |
| { |
| public required IPlugin Plugin { get; init; } |
| public required Assembly Assembly { get; init; } |
| public required string FilePath { get; init; } |
| public required PluginMetadata Metadata { get; init; } |
| public bool IsRunning { get; set; } |
| } |
|
|
| |
| |
| |
| public record PluginMetadata(string Id, string Name, string Description); |
|
|