]> git.deb.at Git - rhonda/impostor.git/commitdiff
Improve PluginLoader and Enable/Disable plugins
authorAeonLucid <aeonlucid@outlook.com>
Sat, 24 Oct 2020 04:02:52 +0000 (06:02 +0200)
committerAeonLucid <aeonlucid@outlook.com>
Sat, 24 Oct 2020 04:02:52 +0000 (06:02 +0200)
15 files changed:
src/Impostor.Api/Plugins/IPlugin.cs
src/Impostor.Api/Plugins/IPluginStartup.cs [new file with mode: 0644]
src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs [new file with mode: 0644]
src/Impostor.Api/Plugins/PluginBase.cs
src/Impostor.Plugins.Debugger/DebugPlugin.cs
src/Impostor.Plugins.Debugger/DebugPluginStartup.cs [new file with mode: 0644]
src/Impostor.Plugins.Example/ExamplePlugin.cs [new file with mode: 0644]
src/Impostor.Plugins.Example/Impostor.Plugins.Example.csproj [new file with mode: 0644]
src/Impostor.Server/Impostor.Server.csproj
src/Impostor.Server/Plugins/PluginInformation.cs [new file with mode: 0644]
src/Impostor.Server/Plugins/PluginLoader.cs
src/Impostor.Server/Plugins/PluginLoaderException.cs [new file with mode: 0644]
src/Impostor.Server/Plugins/PluginLoaderService.cs [new file with mode: 0644]
src/Impostor.Server/Program.cs
src/Impostor.sln

index f66f112d7844221b65c27ee176dcee6ccae5a1b5..a4c3a5512821372180cb8763bf5ad0896b339e61 100644 (file)
@@ -12,9 +12,5 @@ namespace Impostor.Api.Plugins
         ValueTask DisableAsync();
 
         ValueTask ReloadAsync();
-
-        void ConfigureHost(IHostBuilder host);
-
-        void ConfigureServices(IServiceCollection services);
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Api/Plugins/IPluginStartup.cs b/src/Impostor.Api/Plugins/IPluginStartup.cs
new file mode 100644 (file)
index 0000000..aa6a35f
--- /dev/null
@@ -0,0 +1,12 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Impostor.Api.Plugins
+{
+    public interface IPluginStartup
+    {
+        void ConfigureHost(IHostBuilder host);
+
+        void ConfigureServices(IServiceCollection services);
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs b/src/Impostor.Api/Plugins/ImpostorPluginAttribute.cs
new file mode 100644 (file)
index 0000000..b31bd47
--- /dev/null
@@ -0,0 +1,24 @@
+using System;
+
+namespace Impostor.Api.Plugins
+{
+    [AttributeUsage(AttributeTargets.Class)]
+    public class ImpostorPluginAttribute : Attribute
+    {
+        public ImpostorPluginAttribute(string package, string name, string author, string version)
+        {
+            Package = package;
+            Name = name;
+            Author = author;
+            Version = version;
+        }
+
+        public string Package { get; }
+
+        public string Name { get; }
+
+        public string Author { get; }
+
+        public string Version { get; }
+    }
+}
\ No newline at end of file
index cf641905e1f48d800b6b4ddeff7efdd8e91345ba..03843638f556a7dea5bf8b79a98213979cd08689 100644 (file)
@@ -1,6 +1,4 @@
 using System.Threading.Tasks;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
 
 namespace Impostor.Api.Plugins
 {
@@ -20,13 +18,5 @@ namespace Impostor.Api.Plugins
         {
             return default;
         }
-
-        public virtual void ConfigureHost(IHostBuilder host)
-        {
-        }
-
-        public virtual void ConfigureServices(IServiceCollection services)
-        {
-        }
     }
 }
\ No newline at end of file
index 3521a6542d073ae08a4e36a1d000e301606d36e6..8f57e89e0c26a7e587660e741c922b6e4a6f70de 100644 (file)
@@ -1,35 +1,13 @@
 using Impostor.Api.Plugins;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
 
 namespace Impostor.Plugins.Debugger
 {
+    [ImpostorPlugin(
+        package: "gg.impostor.debugger",
+        name: "Debugger",
+        author: "Gerard",
+        version: "1.0.0")]
     public class DebugPlugin : PluginBase
     {
-        public override void ConfigureServices(IServiceCollection services)
-        {
-            services.AddRazorPages();
-            services.AddServerSideBlazor();
-        }
-
-        public override void ConfigureHost(IHostBuilder host)
-        {
-            host.ConfigureWebHostDefaults(webBuilder =>
-            {
-                webBuilder.Configure(app =>
-                {
-                    app.UseStaticFiles();
-                    app.UseRouting();
-
-                    app.UseEndpoints(endpoints =>
-                    {
-                        endpoints.MapBlazorHub();
-                        endpoints.MapFallbackToPage("/_Host");
-                    });
-                });
-            });
-        }
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Plugins.Debugger/DebugPluginStartup.cs b/src/Impostor.Plugins.Debugger/DebugPluginStartup.cs
new file mode 100644 (file)
index 0000000..cc36ce6
--- /dev/null
@@ -0,0 +1,35 @@
+using Impostor.Api.Plugins;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Impostor.Plugins.Debugger
+{
+    public class DebugPluginStartup : IPluginStartup
+    {
+        public void ConfigureServices(IServiceCollection services)
+        {
+            services.AddRazorPages();
+            services.AddServerSideBlazor();
+        }
+
+        public void ConfigureHost(IHostBuilder host)
+        {
+            host.ConfigureWebHostDefaults(webBuilder =>
+            {
+                webBuilder.Configure(app =>
+                {
+                    app.UseStaticFiles();
+                    app.UseRouting();
+
+                    app.UseEndpoints(endpoints =>
+                    {
+                        endpoints.MapBlazorHub();
+                        endpoints.MapFallbackToPage("/_Host");
+                    });
+                });
+            });
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Plugins.Example/ExamplePlugin.cs b/src/Impostor.Plugins.Example/ExamplePlugin.cs
new file mode 100644 (file)
index 0000000..c55039c
--- /dev/null
@@ -0,0 +1,33 @@
+using System.Threading.Tasks;
+using Impostor.Api.Plugins;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Plugins.Example
+{
+    [ImpostorPlugin(
+        package: "gg.impostor.example",
+        name: "Example",
+        author: "AeonLucid",
+        version: "1.0.0")]
+    public class ExamplePlugin : PluginBase
+    {
+        private readonly ILogger<ExamplePlugin> _logger;
+
+        public ExamplePlugin(ILogger<ExamplePlugin> logger)
+        {
+            _logger = logger;
+        }
+
+        public override ValueTask EnableAsync()
+        {
+            _logger.LogInformation("Hooray.");
+            return default;
+        }
+
+        public override ValueTask DisableAsync()
+        {
+            _logger.LogInformation("Boooh.");
+            return default;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Plugins.Example/Impostor.Plugins.Example.csproj b/src/Impostor.Plugins.Example/Impostor.Plugins.Example.csproj
new file mode 100644 (file)
index 0000000..4d93f83
--- /dev/null
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>netstandard2.1</TargetFramework>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\Impostor.Api\Impostor.Api.csproj" />
+    </ItemGroup>
+
+</Project>
index cbcbf74cfb2992e3b21c4adf6123d050b3847723..5f69bf1e50eea1c1cad88d9fdd9927c99b728f79 100644 (file)
@@ -44,7 +44,7 @@
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
             <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
         </Content>
-        <Content Update="config.Development.json">
+        <Content Include="config.Development.json">
             <CopyToPublishDirectory>Never</CopyToPublishDirectory>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
             <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
diff --git a/src/Impostor.Server/Plugins/PluginInformation.cs b/src/Impostor.Server/Plugins/PluginInformation.cs
new file mode 100644 (file)
index 0000000..e6a5b6c
--- /dev/null
@@ -0,0 +1,38 @@
+using System;
+using System.Reflection;
+using Impostor.Api.Plugins;
+
+namespace Impostor.Server.Plugins
+{
+    public class PluginInformation
+    {
+        private readonly ImpostorPluginAttribute _attribute;
+
+        public PluginInformation(IPluginStartup startup, Type pluginType)
+        {
+            _attribute = pluginType.GetCustomAttribute<ImpostorPluginAttribute>();
+
+            Startup = startup;
+            PluginType = pluginType;
+        }
+
+        public string Package => _attribute.Package;
+
+        public string Name => _attribute.Name;
+
+        public string Author => _attribute.Author;
+
+        public string Version => _attribute.Version;
+
+        public IPluginStartup Startup { get; }
+
+        public Type PluginType { get; }
+
+        public IPlugin Instance { get; set; }
+
+        public override string ToString()
+        {
+            return $"{Package} {Name} ({Version}) by {Author}";
+        }
+    }
+}
\ No newline at end of file
index 2b3985dd86e02ae081eaf35bfaff3d3d10091100..9705567c8f843493aabc5b948087fc4fc4b97d9d 100644 (file)
@@ -5,6 +5,7 @@ using System.Linq;
 using System.Reflection;
 using System.Runtime.Loader;
 using Impostor.Api.Plugins;
+using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.FileSystemGlobbing;
 using Microsoft.Extensions.Hosting;
 
@@ -50,23 +51,58 @@ namespace Impostor.Server.Plugins
                 .Select(a => context.LoadFromAssemblyName(a.AssemblyName))
                 .ToList();
 
-            var plugins = assemblies
-                .SelectMany(a => a.GetTypes())
-                .Where(t => typeof(IPlugin).IsAssignableFrom(t) && t.IsClass && !t.IsAbstract)
-                .Select(Activator.CreateInstance)
-                .Cast<IPlugin>()
-                .ToList();
 
-            foreach (var plugin in plugins)
+            // Find all plugins.
+            var plugins = new List<PluginInformation>();
+
+            foreach (var assembly in assemblies)
+            {
+                // Find plugin startup.
+                var pluginStartup = assembly
+                    .GetTypes()
+                    .Where(t => typeof(IPluginStartup).IsAssignableFrom(t) && t.IsClass)
+                    .ToList();
+
+                if (pluginStartup.Count > 1)
+                {
+                    throw new PluginLoaderException("A plugin may only define zero or one IPluginStartup implementation.");
+                }
+
+                // Find plugin.
+                var plugin = assembly
+                    .GetTypes()
+                    .Where(t => typeof(IPlugin).IsAssignableFrom(t)
+                                && t.IsClass
+                                && !t.IsAbstract
+                                && t.GetCustomAttribute<ImpostorPluginAttribute>() != null)
+                    .ToList();
+
+                if (plugin.Count != 1)
+                {
+                    throw new PluginLoaderException("A plugin must define exactly one IPlugin or PluginBase implementation.");
+                }
+
+                // Save plugin.
+                plugins.Add(new PluginInformation(
+                    pluginStartup
+                        .Select(Activator.CreateInstance)
+                        .Cast<IPluginStartup>()
+                        .FirstOrDefault(),
+                    plugin.First()));
+            }
+
+            foreach (var plugin in plugins.Where(plugin => plugin.Startup != null))
             {
-                plugin.ConfigureHost(builder);
+                plugin.Startup.ConfigureHost(builder);
             }
 
             builder.ConfigureServices(services =>
             {
-                foreach (var plugin in plugins)
+                services.AddHostedService(provider => ActivatorUtilities.CreateInstance<PluginLoaderService>(provider, plugins));
+
+                foreach (var plugin in plugins.Where(plugin => plugin.Startup != null))
                 {
-                    plugin.ConfigureServices(services);
+                    plugin.Startup.ConfigureServices(services);
                 }
             });
 
diff --git a/src/Impostor.Server/Plugins/PluginLoaderException.cs b/src/Impostor.Server/Plugins/PluginLoaderException.cs
new file mode 100644 (file)
index 0000000..64424a1
--- /dev/null
@@ -0,0 +1,25 @@
+using System;
+using System.Runtime.Serialization;
+using Impostor.Api;
+
+namespace Impostor.Server.Plugins
+{
+    public class PluginLoaderException : ImpostorException
+    {
+        public PluginLoaderException()
+        {
+        }
+
+        protected PluginLoaderException(SerializationInfo info, StreamingContext context) : base(info, context)
+        {
+        }
+
+        public PluginLoaderException(string? message) : base(message)
+        {
+        }
+
+        public PluginLoaderException(string? message, Exception? innerException) : base(message, innerException)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Plugins/PluginLoaderService.cs b/src/Impostor.Server/Plugins/PluginLoaderService.cs
new file mode 100644 (file)
index 0000000..7ba4b77
--- /dev/null
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Impostor.Api.Plugins;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Plugins
+{
+    public class PluginLoaderService : IHostedService
+    {
+        private readonly ILogger<PluginLoaderService> _logger;
+        private readonly IServiceProvider _serviceProvider;
+        private readonly List<PluginInformation> _plugins;
+
+        public PluginLoaderService(ILogger<PluginLoaderService> logger, IServiceProvider serviceProvider, List<PluginInformation> plugins)
+        {
+            _logger = logger;
+            _serviceProvider = serviceProvider;
+            _plugins = plugins;
+        }
+
+        public async Task StartAsync(CancellationToken cancellationToken)
+        {
+            foreach (var plugin in _plugins)
+            {
+                _logger.LogInformation("Enabling plugin {0}", plugin);
+
+                // Create instance and inject services.
+                plugin.Instance = (IPlugin) ActivatorUtilities.CreateInstance(_serviceProvider, plugin.PluginType);
+
+                // Enable plugin.
+                await plugin.Instance.EnableAsync();
+            }
+        }
+
+        public async Task StopAsync(CancellationToken cancellationToken)
+        {
+            // Disable all plugins with a valid instance set.
+            // In the case of a failed startup, some can be null.
+            foreach (var plugin in _plugins.Where(plugin => plugin.Instance != null))
+            {
+                _logger.LogInformation("Disabling plugin {0}", plugin);
+
+                // Disable plugin.
+                await plugin.Instance.DisableAsync();
+            }
+        }
+    }
+}
\ No newline at end of file
index 9c49224af67305f71cc446660b36ac1152115167..8f0fc26f5e0bd085ba7d5b2b22de28bf3a94604c 100644 (file)
@@ -173,9 +173,9 @@ namespace Impostor.Server
                     services.AddSingleton<Matchmaker>();
                     services.AddHostedService<MatchmakerService>();
                 })
-                .UsePluginLoader(pluginConfig)
+                .UseSerilog()
                 .UseConsoleLifetime()
-                .UseSerilog();
+                .UsePluginLoader(pluginConfig);
         }
     }
 }
\ No newline at end of file
index c04c378310f1fe4b14a1e00abc6dd25c6026242a..dcd5255a0dd49b713daf2c666a00b16f70284f80 100644 (file)
@@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Client.App", "Impo
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Tools.ServerReplay", "Impostor.Tools.ServerReplay\Impostor.Tools.ServerReplay.csproj", "{4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Plugins.Example", "Impostor.Plugins.Example\Impostor.Plugins.Example.csproj", "{70F97BB7-12A1-4C7A-B2A5-B962D14B813E}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
@@ -135,6 +137,14 @@ Global
                {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Release|Any CPU.Build.0 = Release|Any CPU
                {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Release|x86.ActiveCfg = Release|Any CPU
                {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Release|x86.Build.0 = Release|Any CPU
+               {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Debug|x86.Build.0 = Debug|Any CPU
+               {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|Any CPU.Build.0 = Release|Any CPU
+               {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x86.ActiveCfg = Release|Any CPU
+               {70F97BB7-12A1-4C7A-B2A5-B962D14B813E}.Release|x86.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
@@ -151,5 +161,6 @@ Global
                {BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB} = {9F1919B0-915B-4749-9944-697DF7E7F67F}
                {3DF86F12-7099-44F6-B98B-A148213D60B1} = {9F1919B0-915B-4749-9944-697DF7E7F67F}
                {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99} = {56DD9707-D811-4056-9E2C-8A9CC2479B07}
+               {70F97BB7-12A1-4C7A-B2A5-B962D14B813E} = {36AA9913-E6EA-4A6C-90E6-2FD3CC2E3124}
        EndGlobalSection
 EndGlobal