mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-28 09:39:04 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e857ed555 | ||
|
|
9d31752bf0 |
No files matched your search
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Impostor.Api.Plugins
|
||||||
|
{
|
||||||
|
public interface IPluginInformation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unique id of this plugin.
|
||||||
|
/// </summary>
|
||||||
|
string Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the human readable name of this plugin.
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the author of this plugin.
|
||||||
|
/// </summary>
|
||||||
|
string Author { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the version of this plugin.
|
||||||
|
/// </summary>
|
||||||
|
string Version { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the optional startup object of this plugin.
|
||||||
|
/// </summary>
|
||||||
|
IPluginStartup? Startup { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance of this plugin.
|
||||||
|
/// </summary>
|
||||||
|
IPlugin? Instance { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance type of this plugin.
|
||||||
|
/// </summary>
|
||||||
|
Type PluginType { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Impostor.Api.Plugins
|
||||||
|
{
|
||||||
|
public interface IPluginManager
|
||||||
|
{
|
||||||
|
IReadOnlyList<IPluginInformation> Plugins { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Impostor.Api.Games.Managers;
|
using Impostor.Api.Games.Managers;
|
||||||
using Impostor.Api.Innersloth;
|
using Impostor.Api.Innersloth;
|
||||||
@@ -11,17 +12,21 @@ namespace Impostor.Plugins.Example
|
|||||||
{
|
{
|
||||||
private readonly ILogger<ExamplePlugin> _logger;
|
private readonly ILogger<ExamplePlugin> _logger;
|
||||||
private readonly IGameManager _gameManager;
|
private readonly IGameManager _gameManager;
|
||||||
|
private readonly IPluginManager _pluginManager;
|
||||||
|
|
||||||
public ExamplePlugin(ILogger<ExamplePlugin> logger, IGameManager gameManager)
|
public ExamplePlugin(ILogger<ExamplePlugin> logger, IGameManager gameManager, IPluginManager pluginManager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_gameManager = gameManager;
|
_gameManager = gameManager;
|
||||||
|
_pluginManager = pluginManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async ValueTask EnableAsync()
|
public override async ValueTask EnableAsync()
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Example is being enabled.");
|
_logger.LogInformation("Example is being enabled.");
|
||||||
|
|
||||||
|
_logger.LogInformation("Following plugins are installed: {@plugins}", _pluginManager.Plugins.Select(x => $"{x.Id} ({x.Version})"));
|
||||||
|
|
||||||
var game = await _gameManager.CreateAsync(new GameOptionsData());
|
var game = await _gameManager.CreateAsync(new GameOptionsData());
|
||||||
if (game == null)
|
if (game == null)
|
||||||
{
|
{
|
||||||
|
|||||||
+4
-2
@@ -1,9 +1,11 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Impostor.Server.Plugins
|
namespace Impostor.Server.Config
|
||||||
{
|
{
|
||||||
public class PluginConfig
|
public class PluginLoaderConfig
|
||||||
{
|
{
|
||||||
|
public const string Section = "PluginLoader";
|
||||||
|
|
||||||
public List<string> Paths { get; set; } = new List<string>();
|
public List<string> Paths { get; set; } = new List<string>();
|
||||||
|
|
||||||
public List<string> LibraryPaths { get; set; } = new List<string>();
|
public List<string> LibraryPaths { get; set; } = new List<string>();
|
||||||
@@ -26,10 +26,12 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="5.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="5.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="5.0.4" />
|
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="5.0.7" />
|
||||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.1.2" />
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.1.2" />
|
||||||
|
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
|
||||||
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.333">
|
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.333">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@@ -8,17 +8,20 @@ namespace Impostor.Server.Plugins
|
|||||||
{
|
{
|
||||||
private Assembly? _assembly;
|
private Assembly? _assembly;
|
||||||
|
|
||||||
public AssemblyInformation(AssemblyName assemblyName, string path, bool isPlugin)
|
public AssemblyInformation(AssemblyName assemblyName, string path, bool isPlugin, bool isDefaultAssembly)
|
||||||
{
|
{
|
||||||
AssemblyName = assemblyName;
|
AssemblyName = assemblyName;
|
||||||
Path = path;
|
Path = path;
|
||||||
IsPlugin = isPlugin;
|
IsPlugin = isPlugin;
|
||||||
|
IsDefaultAssembly = isDefaultAssembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
|
|
||||||
public bool IsPlugin { get; }
|
public bool IsPlugin { get; }
|
||||||
|
|
||||||
|
public bool IsDefaultAssembly { get; }
|
||||||
|
|
||||||
public AssemblyName AssemblyName { get; }
|
public AssemblyName AssemblyName { get; }
|
||||||
|
|
||||||
public Assembly Load(AssemblyLoadContext context)
|
public Assembly Load(AssemblyLoadContext context)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ namespace Impostor.Server.Plugins
|
|||||||
|
|
||||||
bool IsPlugin { get; }
|
bool IsPlugin { get; }
|
||||||
|
|
||||||
|
bool IsDefaultAssembly { get; }
|
||||||
|
|
||||||
Assembly Load(AssemblyLoadContext context);
|
Assembly Load(AssemblyLoadContext context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,16 +7,19 @@ namespace Impostor.Server.Plugins
|
|||||||
{
|
{
|
||||||
private readonly Assembly _assembly;
|
private readonly Assembly _assembly;
|
||||||
|
|
||||||
public LoadedAssemblyInformation(Assembly assembly)
|
public LoadedAssemblyInformation(Assembly assembly, bool isDefaultAssembly)
|
||||||
{
|
{
|
||||||
AssemblyName = assembly.GetName();
|
AssemblyName = assembly.GetName();
|
||||||
_assembly = assembly;
|
_assembly = assembly;
|
||||||
|
IsDefaultAssembly = isDefaultAssembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AssemblyName AssemblyName { get; }
|
public AssemblyName AssemblyName { get; }
|
||||||
|
|
||||||
public bool IsPlugin => false;
|
public bool IsPlugin => false;
|
||||||
|
|
||||||
|
public bool IsDefaultAssembly { get; }
|
||||||
|
|
||||||
public Assembly Load(AssemblyLoadContext context)
|
public Assembly Load(AssemblyLoadContext context)
|
||||||
{
|
{
|
||||||
return _assembly;
|
return _assembly;
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
|
||||||
|
namespace Impostor.Server.Plugins
|
||||||
|
{
|
||||||
|
public class PluginAssemblyLoadContext : AssemblyLoadContext, IDisposable
|
||||||
|
{
|
||||||
|
private readonly IDictionary<string, IAssemblyInformation> _assembliesByName;
|
||||||
|
|
||||||
|
public PluginAssemblyLoadContext(IDictionary<string, IAssemblyInformation> assembliesByName)
|
||||||
|
: base(true)
|
||||||
|
{
|
||||||
|
_assembliesByName = assembliesByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_assembliesByName.Clear();
|
||||||
|
this.Unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Assembly? Load(AssemblyName name)
|
||||||
|
{
|
||||||
|
return name.Name != null && _assembliesByName.TryGetValue(name.Name, out var assemblyInfo)
|
||||||
|
? assemblyInfo.Load(this)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ using Impostor.Api.Plugins;
|
|||||||
|
|
||||||
namespace Impostor.Server.Plugins
|
namespace Impostor.Server.Plugins
|
||||||
{
|
{
|
||||||
public class PluginInformation
|
public class PluginInformation : IPluginInformation
|
||||||
{
|
{
|
||||||
private readonly ImpostorPluginAttribute _attribute;
|
private readonly ImpostorPluginAttribute _attribute;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Impostor.Api.Plugins;
|
using Impostor.Api.Plugins;
|
||||||
@@ -13,20 +12,20 @@ namespace Impostor.Server.Plugins
|
|||||||
{
|
{
|
||||||
private readonly ILogger<PluginLoaderService> _logger;
|
private readonly ILogger<PluginLoaderService> _logger;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly List<PluginInformation> _plugins;
|
private readonly PluginManager _pluginManager;
|
||||||
|
|
||||||
public PluginLoaderService(ILogger<PluginLoaderService> logger, IServiceProvider serviceProvider, List<PluginInformation> plugins)
|
public PluginLoaderService(ILogger<PluginLoaderService> logger, IServiceProvider serviceProvider, PluginManager pluginManager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
_plugins = plugins;
|
_pluginManager = pluginManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Loading plugins.");
|
_logger.LogInformation("Enabling plugins.");
|
||||||
|
|
||||||
foreach (var plugin in _plugins)
|
foreach (var plugin in _pluginManager.Plugins)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Enabling plugin {0}.", plugin);
|
_logger.LogInformation("Enabling plugin {0}.", plugin);
|
||||||
|
|
||||||
@@ -38,10 +37,10 @@ namespace Impostor.Server.Plugins
|
|||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
_plugins.Count == 1
|
_pluginManager.Plugins.Count == 1
|
||||||
? "Loaded {0} plugin."
|
? "Enabled {0} plugin."
|
||||||
: "Loaded {0} plugins.",
|
: "Enabled {0} plugins.",
|
||||||
_plugins.Count
|
_pluginManager.Plugins.Count
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +48,7 @@ namespace Impostor.Server.Plugins
|
|||||||
{
|
{
|
||||||
// Disable all plugins with a valid instance set.
|
// Disable all plugins with a valid instance set.
|
||||||
// In the case of a failed startup, some can be null.
|
// In the case of a failed startup, some can be null.
|
||||||
foreach (var plugin in _plugins)
|
foreach (var plugin in _pluginManager.Plugins)
|
||||||
{
|
{
|
||||||
if (plugin.Instance != null)
|
if (plugin.Instance != null)
|
||||||
{
|
{
|
||||||
@@ -57,6 +56,7 @@ namespace Impostor.Server.Plugins
|
|||||||
|
|
||||||
// Disable plugin.
|
// Disable plugin.
|
||||||
await plugin.Instance.DisableAsync();
|
await plugin.Instance.DisableAsync();
|
||||||
|
plugin.Instance = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+150
-56
@@ -4,24 +4,72 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Loader;
|
using System.Runtime.Loader;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Impostor.Api.Plugins;
|
using Impostor.Api.Plugins;
|
||||||
|
using Impostor.Server.Config;
|
||||||
|
using Impostor.Server.Plugins.Proxies;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.FileSystemGlobbing;
|
using Microsoft.Extensions.FileSystemGlobbing;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Serilog;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Impostor.Server.Plugins
|
namespace Impostor.Server.Plugins
|
||||||
{
|
{
|
||||||
public static class PluginLoader
|
public class PluginManager : IPluginManager, IHostedService
|
||||||
{
|
{
|
||||||
private static readonly ILogger Logger = Log.ForContext(typeof(PluginLoader));
|
private readonly ILogger<PluginManager> _logger;
|
||||||
|
private readonly IOptions<PluginLoaderConfig> _pluginConfig;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly ServiceProxyCollection _serviceProxyCollection;
|
||||||
|
|
||||||
public static IHostBuilder UsePluginLoader(this IHostBuilder builder, PluginConfig config)
|
private PluginAssemblyLoadContext? _currentContext;
|
||||||
|
|
||||||
|
public PluginManager(ILogger<PluginManager> logger, IOptions<PluginLoaderConfig> pluginConfig, IConfiguration configuration, IServiceProvider serviceProvider, ServiceProxyCollection serviceProxyCollection)
|
||||||
{
|
{
|
||||||
var assemblyInfos = new List<IAssemblyInformation>();
|
_logger = logger;
|
||||||
var context = AssemblyLoadContext.Default;
|
_pluginConfig = pluginConfig;
|
||||||
|
_configuration = configuration;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_serviceProxyCollection = serviceProxyCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IHost? CurrentHost { get; private set; }
|
||||||
|
|
||||||
|
public List<PluginInformation> Plugins { get; } = new List<PluginInformation>();
|
||||||
|
|
||||||
|
IReadOnlyList<IPluginInformation> IPluginManager.Plugins => Plugins.AsReadOnly();
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
CurrentHost = CreatePluginHost();
|
||||||
|
return CurrentHost.StartAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (CurrentHost != null)
|
||||||
|
{
|
||||||
|
await CurrentHost.StopAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (CurrentHost is IAsyncDisposable asyncDisposable)
|
||||||
|
{
|
||||||
|
await asyncDisposable.DisposeAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentHost.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDictionary<string, IAssemblyInformation> GetAssemblyInformation()
|
||||||
|
{
|
||||||
// Add the plugins and libraries.
|
// Add the plugins and libraries.
|
||||||
|
var config = _pluginConfig.Value;
|
||||||
var pluginPaths = new List<string>(config.Paths);
|
var pluginPaths = new List<string>(config.Paths);
|
||||||
var libraryPaths = new List<string>(config.LibraryPaths);
|
var libraryPaths = new List<string>(config.LibraryPaths);
|
||||||
CheckPaths(pluginPaths);
|
CheckPaths(pluginPaths);
|
||||||
@@ -35,38 +83,70 @@ namespace Impostor.Server.Plugins
|
|||||||
var matcher = new Matcher(StringComparison.OrdinalIgnoreCase);
|
var matcher = new Matcher(StringComparison.OrdinalIgnoreCase);
|
||||||
matcher.AddInclude("*.dll");
|
matcher.AddInclude("*.dll");
|
||||||
matcher.AddExclude("Impostor.Api.dll");
|
matcher.AddExclude("Impostor.Api.dll");
|
||||||
|
matcher.AddExclude("Impostor.Server.dll");
|
||||||
|
|
||||||
RegisterAssemblies(pluginPaths, matcher, assemblyInfos, true);
|
var assembliesByName = AssemblyLoadContext.Default.Assemblies
|
||||||
RegisterAssemblies(libraryPaths, matcher, assemblyInfos, false);
|
.Where(a => a.GetName().Name != null)
|
||||||
|
.ToDictionary(
|
||||||
|
a => a.GetName().Name!,
|
||||||
|
a => (IAssemblyInformation)new LoadedAssemblyInformation(a, true));
|
||||||
|
|
||||||
// Register the resolver to the current context.
|
RegisterAssemblies(assembliesByName, pluginPaths, matcher, true);
|
||||||
// TODO: Move this to a new context so we can unload/reload plugins.
|
RegisterAssemblies(assembliesByName, libraryPaths, matcher, false);
|
||||||
context.Resolving += (loadContext, name) =>
|
|
||||||
|
return assembliesByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IHost CreatePluginHost()
|
||||||
|
{
|
||||||
|
var assembliesByName = GetAssemblyInformation();
|
||||||
|
_currentContext ??= new PluginAssemblyLoadContext(assembliesByName);
|
||||||
|
|
||||||
|
var pluginHostBuilder = new HostBuilder()
|
||||||
|
.UseConsoleLifetime(options => options.SuppressStatusMessages = true)
|
||||||
|
.UseContentRoot(_configuration.GetValue<string>(HostDefaults.ContentRootKey))
|
||||||
|
.ConfigureHostConfiguration(builder => { builder.AddConfiguration(_configuration); })
|
||||||
|
.UseServiceProviderFactory(_ => new DefaultServiceProviderFactory());
|
||||||
|
|
||||||
|
Plugins.Clear();
|
||||||
|
Plugins.AddRange(GetPluginInformation(assembliesByName, _currentContext));
|
||||||
|
|
||||||
|
foreach (var plugin in Plugins)
|
||||||
{
|
{
|
||||||
Logger.Verbose("Loading assembly {0} v{1}", name.Name, name.Version);
|
plugin.Startup?.ConfigureHost(pluginHostBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
// Some plugins may be referencing another Impostor.Api version and try to load it.
|
pluginHostBuilder.ConfigureServices(services =>
|
||||||
// We want to only use the one shipped with the server.
|
{
|
||||||
if (name.Name == "Impostor.Api")
|
services.AddHostedService<PluginLoaderService>();
|
||||||
|
|
||||||
|
foreach (var (type, lifetime) in _serviceProxyCollection.ServiceTypes)
|
||||||
{
|
{
|
||||||
return typeof(IPlugin).Assembly;
|
services.Add(new ServiceDescriptor(
|
||||||
|
type,
|
||||||
|
_ => _serviceProvider.GetService(type)!,
|
||||||
|
lifetime));
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = assemblyInfos.FirstOrDefault(a => a.AssemblyName.Name == name.Name);
|
foreach (var plugin in Plugins)
|
||||||
|
{
|
||||||
|
plugin.Startup?.ConfigureServices(services);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return info?.Load(loadContext);
|
return pluginHostBuilder.Build();
|
||||||
};
|
}
|
||||||
|
|
||||||
// TODO: Catch uncaught exceptions.
|
private List<PluginInformation> GetPluginInformation(IDictionary<string, IAssemblyInformation> assembliesByName, PluginAssemblyLoadContext context)
|
||||||
var assemblies = assemblyInfos
|
{
|
||||||
|
var plugins = new List<PluginInformation>();
|
||||||
|
var pluginAssemblies = assembliesByName
|
||||||
|
.Values
|
||||||
.Where(a => a.IsPlugin)
|
.Where(a => a.IsPlugin)
|
||||||
.Select(a => context.LoadFromAssemblyName(a.AssemblyName))
|
.Select(a => context.LoadFromAssemblyName(a.AssemblyName))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Find all plugins.
|
foreach (var assembly in pluginAssemblies)
|
||||||
var plugins = new List<PluginInformation>();
|
|
||||||
|
|
||||||
foreach (var assembly in assemblies)
|
|
||||||
{
|
{
|
||||||
// Find plugin startup.
|
// Find plugin startup.
|
||||||
var pluginStartup = assembly
|
var pluginStartup = assembly
|
||||||
@@ -76,7 +156,7 @@ namespace Impostor.Server.Plugins
|
|||||||
|
|
||||||
if (pluginStartup.Count > 1)
|
if (pluginStartup.Count > 1)
|
||||||
{
|
{
|
||||||
Logger.Warning("A plugin may only define zero or one IPluginStartup implementation ({0}).", assembly);
|
_logger.LogWarning("A plugin may only define zero or one IPluginStartup implementation ({0}).", assembly);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +171,7 @@ namespace Impostor.Server.Plugins
|
|||||||
|
|
||||||
if (plugin.Count != 1)
|
if (plugin.Count != 1)
|
||||||
{
|
{
|
||||||
Logger.Warning("A plugin must define exactly one IPlugin or PluginBase implementation ({0}).", assembly);
|
_logger.LogWarning("A plugin must define exactly one IPlugin or PluginBase implementation ({0}).", assembly);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,61 +184,75 @@ namespace Impostor.Server.Plugins
|
|||||||
plugin.Single()));
|
plugin.Single()));
|
||||||
}
|
}
|
||||||
|
|
||||||
var orderedPlugins = LoadOrderPlugins(plugins);
|
return OrderPlugins(plugins);
|
||||||
|
|
||||||
foreach (var plugin in orderedPlugins)
|
|
||||||
{
|
|
||||||
plugin.Startup?.ConfigureHost(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.ConfigureServices(services =>
|
|
||||||
{
|
|
||||||
services.AddHostedService(provider => ActivatorUtilities.CreateInstance<PluginLoaderService>(provider, orderedPlugins));
|
|
||||||
|
|
||||||
foreach (var plugin in orderedPlugins)
|
|
||||||
{
|
|
||||||
plugin.Startup?.ConfigureServices(services);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CheckPaths(IEnumerable<string> paths)
|
private void CheckPaths(IEnumerable<string> paths)
|
||||||
{
|
{
|
||||||
foreach (var path in paths)
|
foreach (var path in paths)
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(path))
|
if (!Directory.Exists(path))
|
||||||
{
|
{
|
||||||
Logger.Warning("Path {path} was specified in the PluginLoader configuration, but this folder doesn't exist!", path);
|
_logger.LogWarning("Path {path} was specified in the PluginLoader configuration, but this folder doesn't exist!", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterAssemblies(
|
private void RegisterAssemblies(
|
||||||
|
IDictionary<string, IAssemblyInformation> assemblies,
|
||||||
IEnumerable<string> paths,
|
IEnumerable<string> paths,
|
||||||
Matcher matcher,
|
Matcher matcher,
|
||||||
ICollection<IAssemblyInformation> assemblyInfos,
|
|
||||||
bool isPlugin)
|
bool isPlugin)
|
||||||
{
|
{
|
||||||
foreach (var path in paths.SelectMany(matcher.GetResultsInFullPath))
|
foreach (var path in paths.SelectMany(matcher.GetResultsInFullPath))
|
||||||
{
|
{
|
||||||
AssemblyName assemblyName;
|
AssemblyName name;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
assemblyName = AssemblyName.GetAssemblyName(path);
|
name = AssemblyName.GetAssemblyName(path);
|
||||||
}
|
}
|
||||||
catch (BadImageFormatException)
|
catch (BadImageFormatException)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
assemblyInfos.Add(new AssemblyInformation(assemblyName, path, isPlugin));
|
if (name.Name == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Skipped {0} because the assembly name could not be resolved",
|
||||||
|
path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assemblies.TryGetValue(name.Name, out var loaded))
|
||||||
|
{
|
||||||
|
var version = name.Version;
|
||||||
|
var loadedVersion = loaded.AssemblyName.Version;
|
||||||
|
|
||||||
|
if (loadedVersion != null && version != null && version.Major != loadedVersion.Major)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
"Assembly {0} is already loaded with version {1} while loading version {2}",
|
||||||
|
name.Name,
|
||||||
|
loadedVersion,
|
||||||
|
version);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Skipped assembly {0} because it's already loaded", name.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogTrace("Registered assembly {name} v{version} ({path})", name.Name, name.Version, path);
|
||||||
|
|
||||||
|
assemblies.Add(name.Name, new AssemblyInformation(name, path, isPlugin, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<PluginInformation> LoadOrderPlugins(IEnumerable<PluginInformation> plugins)
|
private List<PluginInformation> OrderPlugins(IEnumerable<PluginInformation> plugins)
|
||||||
{
|
{
|
||||||
var pluginDictionary = new Dictionary<string, PluginInformation>();
|
var pluginDictionary = new Dictionary<string, PluginInformation>();
|
||||||
var hardDependencies = new Dictionary<string, List<string>>();
|
var hardDependencies = new Dictionary<string, List<string>>();
|
||||||
@@ -208,7 +302,7 @@ namespace Impostor.Server.Plugins
|
|||||||
return ordered;
|
return ordered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<string> CheckHardDependencies(
|
private List<string> CheckHardDependencies(
|
||||||
List<string> plugins,
|
List<string> plugins,
|
||||||
IReadOnlyDictionary<string, List<string>> hardDependencies)
|
IReadOnlyDictionary<string, List<string>> hardDependencies)
|
||||||
{
|
{
|
||||||
@@ -221,7 +315,7 @@ namespace Impostor.Server.Plugins
|
|||||||
|
|
||||||
foreach (var dependency in hardDependencies[plugin].Where(dependency => !plugins.Contains(dependency)))
|
foreach (var dependency in hardDependencies[plugin].Where(dependency => !plugins.Contains(dependency)))
|
||||||
{
|
{
|
||||||
Logger.Error(
|
_logger.LogError(
|
||||||
"The plugin {plugin} has defined the plugin {dependency} as a hard dependency but its not present! {plugin} will not loaded.",
|
"The plugin {plugin} has defined the plugin {dependency} as a hard dependency but its not present! {plugin} will not loaded.",
|
||||||
plugin,
|
plugin,
|
||||||
dependency,
|
dependency,
|
||||||
@@ -239,7 +333,7 @@ namespace Impostor.Server.Plugins
|
|||||||
return plugins;
|
return plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RecursiveOrder(
|
private void RecursiveOrder(
|
||||||
string plugin,
|
string plugin,
|
||||||
IReadOnlyDictionary<string, List<string>> dependencyGraph,
|
IReadOnlyDictionary<string, List<string>> dependencyGraph,
|
||||||
ICollection<string> processed,
|
ICollection<string> processed,
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Impostor.Server.Plugins.Proxies
|
||||||
|
{
|
||||||
|
public class ServiceProxyCollection
|
||||||
|
{
|
||||||
|
public ServiceProxyCollection(IReadOnlyList<ServiceRegistration> serviceTypes)
|
||||||
|
{
|
||||||
|
ServiceTypes = serviceTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<ServiceRegistration> ServiceTypes { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Impostor.Server.Plugins.Proxies
|
||||||
|
{
|
||||||
|
public record ServiceRegistration(Type Type, ServiceLifetime Lifetime);
|
||||||
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Impostor.Api.Events;
|
||||||
using Impostor.Api.Events.Managers;
|
using Impostor.Api.Events.Managers;
|
||||||
using Impostor.Api.Games;
|
using Impostor.Api.Games;
|
||||||
using Impostor.Api.Games.Managers;
|
using Impostor.Api.Games.Managers;
|
||||||
using Impostor.Api.Net.Custom;
|
using Impostor.Api.Net.Custom;
|
||||||
using Impostor.Api.Net.Manager;
|
using Impostor.Api.Net.Manager;
|
||||||
using Impostor.Api.Net.Messages;
|
using Impostor.Api.Net.Messages;
|
||||||
|
using Impostor.Api.Plugins;
|
||||||
using Impostor.Api.Utils;
|
using Impostor.Api.Utils;
|
||||||
using Impostor.Hazel.Extensions;
|
using Impostor.Hazel.Extensions;
|
||||||
using Impostor.Server.Config;
|
using Impostor.Server.Config;
|
||||||
@@ -18,11 +21,13 @@ using Impostor.Server.Net.Manager;
|
|||||||
using Impostor.Server.Net.Messages;
|
using Impostor.Server.Net.Messages;
|
||||||
using Impostor.Server.Net.Redirector;
|
using Impostor.Server.Net.Redirector;
|
||||||
using Impostor.Server.Plugins;
|
using Impostor.Server.Plugins;
|
||||||
|
using Impostor.Server.Plugins.Proxies;
|
||||||
using Impostor.Server.Recorder;
|
using Impostor.Server.Recorder;
|
||||||
using Impostor.Server.Utils;
|
using Impostor.Server.Utils;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.ObjectPool;
|
using Microsoft.Extensions.ObjectPool;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
@@ -31,38 +36,16 @@ namespace Impostor.Server
|
|||||||
{
|
{
|
||||||
internal static class Program
|
internal static class Program
|
||||||
{
|
{
|
||||||
private static int Main(string[] args)
|
private static async Task<int> Main(string[] args)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
|
||||||
var logLevel = LogEventLevel.Debug;
|
|
||||||
#else
|
|
||||||
var logLevel = LogEventLevel.Information;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (args.Contains("--verbose"))
|
|
||||||
{
|
|
||||||
logLevel = LogEventLevel.Verbose;
|
|
||||||
}
|
|
||||||
else if (args.Contains("--errors-only"))
|
|
||||||
{
|
|
||||||
logLevel = LogEventLevel.Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.MinimumLevel.Is(logLevel)
|
|
||||||
#if DEBUG
|
|
||||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Debug)
|
|
||||||
#else
|
|
||||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
|
||||||
#endif
|
|
||||||
.Enrich.FromLogContext()
|
|
||||||
.WriteTo.Console()
|
.WriteTo.Console()
|
||||||
.CreateLogger();
|
.CreateBootstrapLogger();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Log.Information("Starting Impostor v{0}", DotnetUtils.Version);
|
Log.Information("Starting Impostor v{0}", DotnetUtils.Version);
|
||||||
CreateHostBuilder(args).Build().Run();
|
await CreateHostBuilder(args).RunConsoleAsync();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -76,25 +59,8 @@ namespace Impostor.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IConfiguration CreateConfiguration(string[] args)
|
|
||||||
{
|
|
||||||
var configurationBuilder = new ConfigurationBuilder();
|
|
||||||
|
|
||||||
configurationBuilder.SetBasePath(Directory.GetCurrentDirectory());
|
|
||||||
configurationBuilder.AddJsonFile("config.json", true);
|
|
||||||
configurationBuilder.AddJsonFile("config.Development.json", true);
|
|
||||||
configurationBuilder.AddEnvironmentVariables(prefix: "IMPOSTOR_");
|
|
||||||
configurationBuilder.AddCommandLine(args);
|
|
||||||
|
|
||||||
return configurationBuilder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IHostBuilder CreateHostBuilder(string[] args)
|
private static IHostBuilder CreateHostBuilder(string[] args)
|
||||||
{
|
{
|
||||||
var configuration = CreateConfiguration(args);
|
|
||||||
var pluginConfig = configuration.GetSection("PluginLoader")
|
|
||||||
.Get<PluginConfig>() ?? new PluginConfig();
|
|
||||||
|
|
||||||
return Host.CreateDefaultBuilder(args)
|
return Host.CreateDefaultBuilder(args)
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@@ -104,7 +70,10 @@ namespace Impostor.Server
|
|||||||
#endif
|
#endif
|
||||||
.ConfigureAppConfiguration(builder =>
|
.ConfigureAppConfiguration(builder =>
|
||||||
{
|
{
|
||||||
builder.AddConfiguration(configuration);
|
builder.AddJsonFile("config.json", true);
|
||||||
|
builder.AddJsonFile("config.Development.json", true);
|
||||||
|
builder.AddEnvironmentVariables(prefix: "IMPOSTOR_");
|
||||||
|
builder.AddCommandLine(args);
|
||||||
})
|
})
|
||||||
.ConfigureServices((host, services) =>
|
.ConfigureServices((host, services) =>
|
||||||
{
|
{
|
||||||
@@ -134,6 +103,7 @@ namespace Impostor.Server
|
|||||||
services.Configure<AnnouncementsServerConfig>(host.Configuration.GetSection(AnnouncementsServerConfig.Section));
|
services.Configure<AnnouncementsServerConfig>(host.Configuration.GetSection(AnnouncementsServerConfig.Section));
|
||||||
services.Configure<AuthServerConfig>(host.Configuration.GetSection(AuthServerConfig.Section));
|
services.Configure<AuthServerConfig>(host.Configuration.GetSection(AuthServerConfig.Section));
|
||||||
services.Configure<ServerRedirectorConfig>(host.Configuration.GetSection(ServerRedirectorConfig.Section));
|
services.Configure<ServerRedirectorConfig>(host.Configuration.GetSection(ServerRedirectorConfig.Section));
|
||||||
|
services.Configure<PluginLoaderConfig>(host.Configuration.GetSection(PluginLoaderConfig.Section));
|
||||||
|
|
||||||
if (redirector.Enabled)
|
if (redirector.Enabled)
|
||||||
{
|
{
|
||||||
@@ -228,10 +198,53 @@ namespace Impostor.Server
|
|||||||
{
|
{
|
||||||
services.AddHostedService<AuthService>();
|
services.AddHostedService<AuthService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
services.AddSingleton<PluginManager>();
|
||||||
|
services.AddSingleton<IPluginManager>(p => p.GetRequiredService<PluginManager>());
|
||||||
|
services.AddHostedService(p => p.GetRequiredService<PluginManager>());
|
||||||
})
|
})
|
||||||
.UseSerilog()
|
.UseSerilog((context, loggerConfiguration) =>
|
||||||
.UseConsoleLifetime()
|
{
|
||||||
.UsePluginLoader(pluginConfig);
|
#if DEBUG
|
||||||
|
var logLevel = LogEventLevel.Debug;
|
||||||
|
#else
|
||||||
|
var logLevel = LogEventLevel.Information;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (args.Contains("--verbose"))
|
||||||
|
{
|
||||||
|
logLevel = LogEventLevel.Verbose;
|
||||||
|
}
|
||||||
|
else if (args.Contains("--errors-only"))
|
||||||
|
{
|
||||||
|
logLevel = LogEventLevel.Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
loggerConfiguration
|
||||||
|
.MinimumLevel.Is(logLevel)
|
||||||
|
#if DEBUG
|
||||||
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Debug)
|
||||||
|
#else
|
||||||
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||||
|
#endif
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.WriteTo.Console()
|
||||||
|
.ReadFrom.Configuration(context.Configuration);
|
||||||
|
})
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddTransient(serviceProvider => serviceProvider.GetRequiredService<PluginManager>().CurrentHost?.Services.GetServices<IEventListener>() ?? Enumerable.Empty<IEventListener>());
|
||||||
|
|
||||||
|
var apiAssembly = typeof(IPlugin).Assembly;
|
||||||
|
var serilogAssembly = typeof(Serilog.ILogger).Assembly;
|
||||||
|
|
||||||
|
services.AddSingleton(new ServiceProxyCollection(
|
||||||
|
services
|
||||||
|
.Where(t => t.ServiceType.Assembly == apiAssembly || t.ServiceType.Assembly == serilogAssembly || t.ServiceType == typeof(PluginManager) || t.ServiceType == typeof(ILoggerFactory))
|
||||||
|
.Select(t => new ServiceRegistration(t.ServiceType, t.Lifetime))
|
||||||
|
.Distinct()
|
||||||
|
.ToArray()));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user