IW4M-Admin/Application/Plugin/PluginImporter.cs

208 lines
7.7 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
2023-04-04 19:24:13 -04:00
using System.IO;
2018-08-23 17:16:30 -04:00
using System.Linq;
2023-04-04 19:24:13 -04:00
using System.Reflection;
using System.Text.RegularExpressions;
2020-10-24 16:02:38 -04:00
using IW4MAdmin.Application.API.Master;
using Microsoft.Extensions.Logging;
2023-04-04 19:24:13 -04:00
using SharedLibraryCore;
2020-10-24 16:02:38 -04:00
using SharedLibraryCore.Configuration;
2023-04-04 19:24:13 -04:00
using SharedLibraryCore.Interfaces;
using ILogger = Microsoft.Extensions.Logging.ILogger;
2023-04-04 19:24:13 -04:00
namespace IW4MAdmin.Application.Plugin
{
/// <summary>
/// implementation of IPluginImporter
/// discovers plugins and script plugins
/// </summary>
public class PluginImporter : IPluginImporter
{
2020-10-24 16:02:38 -04:00
private IEnumerable<PluginSubscriptionContent> _pluginSubscription;
private const string PluginDir = "Plugins";
2023-04-04 19:24:13 -04:00
private const string PluginV2Match = "^ *((?:var|const|let) +init)|function init";
private readonly ILogger _logger;
2020-10-24 16:02:38 -04:00
private readonly IRemoteAssemblyHandler _remoteAssemblyHandler;
private readonly IMasterApi _masterApi;
private readonly ApplicationConfiguration _appConfig;
private static readonly Type[] FilterTypes =
{
typeof(IPlugin),
typeof(IPluginV2),
typeof(Command),
typeof(IBaseConfiguration)
};
2023-04-04 19:24:13 -04:00
public PluginImporter(ILogger<PluginImporter> logger, ApplicationConfiguration appConfig, IMasterApi masterApi,
IRemoteAssemblyHandler remoteAssemblyHandler)
{
_logger = logger;
2020-10-24 16:02:38 -04:00
_masterApi = masterApi;
_remoteAssemblyHandler = remoteAssemblyHandler;
_appConfig = appConfig;
}
/// <summary>
/// discovers all the script plugins in the plugins dir
/// </summary>
/// <returns></returns>
2023-04-04 19:24:13 -04:00
public IEnumerable<(Type, string)> DiscoverScriptPlugins()
{
2023-04-04 19:24:13 -04:00
var pluginDir = $"{Utilities.OperatingDirectory}{PluginDir}{Path.DirectorySeparatorChar}";
if (!Directory.Exists(pluginDir))
{
2023-04-04 19:24:13 -04:00
return Enumerable.Empty<(Type, string)>();
}
var scriptPluginFiles =
Directory.GetFiles(pluginDir, "*.js").AsEnumerable().Union(GetRemoteScripts()).ToList();
2023-04-04 19:24:13 -04:00
var bothVersionPlugins = scriptPluginFiles.Select(fileName =>
{
2023-04-04 19:24:13 -04:00
_logger.LogDebug("Discovered script plugin {FileName}", fileName);
try
{
var fileContents = File.ReadAllLines(fileName);
var isValidV2 = fileContents.Any(line => Regex.IsMatch(line, PluginV2Match));
return isValidV2 ? (typeof(IPluginV2), fileName) : (typeof(IPlugin), fileName);
}
catch
{
return (typeof(IPlugin), fileName);
}
}).ToList();
2023-04-04 19:24:13 -04:00
return bothVersionPlugins;
}
/// <summary>
/// discovers all the C# assembly plugins and commands
/// </summary>
/// <returns></returns>
public (IEnumerable<Type>, IEnumerable<Type>, IEnumerable<Type>) DiscoverAssemblyPluginImplementations()
{
2023-04-04 19:24:13 -04:00
var pluginDir = $"{Utilities.OperatingDirectory}{PluginDir}{Path.DirectorySeparatorChar}";
var pluginTypes = new List<Type>();
var commandTypes = new List<Type>();
var configurationTypes = new List<Type>();
if (!Directory.Exists(pluginDir))
{
return (pluginTypes, commandTypes, configurationTypes);
}
var dllFileNames = Directory.GetFiles(pluginDir, "*.dll");
_logger.LogDebug("Discovered {Count} potential plugin assemblies", dllFileNames.Length);
if (!dllFileNames.Any())
{
return (pluginTypes, commandTypes, configurationTypes);
}
// we only want to load the most recent assembly in case of duplicates
var assemblies = dllFileNames.Select(Assembly.LoadFrom)
.Union(GetRemoteAssemblies())
.GroupBy(assembly => assembly.FullName).Select(assembly =>
assembly.OrderByDescending(asm => asm.GetName().Version).First());
var eligibleAssemblyTypes = assemblies
.SelectMany(asm =>
{
try
{
return asm.GetTypes();
}
catch
{
return Enumerable.Empty<Type>();
}
}).Where(type =>
FilterTypes.Any(filterType => type.GetInterface(filterType.Name, false) != null) ||
(type.IsClass && FilterTypes.Contains(type.BaseType)));
foreach (var assemblyType in eligibleAssemblyTypes)
{
var isPlugin =
(assemblyType.GetInterface(nameof(IPlugin), false) ??
assemblyType.GetInterface(nameof(IPluginV2), false)) != null &&
(!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
if (isPlugin)
{
pluginTypes.Add(assemblyType);
continue;
}
var isCommand = assemblyType.IsClass && assemblyType.BaseType == typeof(Command) &&
(!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
if (isCommand)
{
commandTypes.Add(assemblyType);
continue;
}
var isConfiguration = assemblyType.IsClass &&
assemblyType.GetInterface(nameof(IBaseConfiguration), false) != null &&
(!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
if (isConfiguration)
{
configurationTypes.Add(assemblyType);
}
}
_logger.LogDebug("Discovered {Count} plugin implementations", pluginTypes.Count);
_logger.LogDebug("Discovered {Count} plugin commands", pluginTypes.Count);
_logger.LogDebug("Discovered {Count} configuration implementations", pluginTypes.Count);
return (pluginTypes, commandTypes, configurationTypes);
}
2020-10-24 16:02:38 -04:00
private IEnumerable<Assembly> GetRemoteAssemblies()
{
try
{
_pluginSubscription ??= _masterApi
.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
2020-10-24 16:02:38 -04:00
return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription
.Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray());
2020-10-24 16:02:38 -04:00
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not load remote assemblies");
2020-10-24 16:02:38 -04:00
return Enumerable.Empty<Assembly>();
}
}
private IEnumerable<string> GetRemoteScripts()
{
try
{
_pluginSubscription ??= _masterApi
.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
2020-10-24 16:02:38 -04:00
return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription
.Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray());
2020-10-24 16:02:38 -04:00
}
catch (Exception ex)
{
_logger.LogWarning(ex,"Could not load remote scripts");
2020-10-24 16:02:38 -04:00
return Enumerable.Empty<string>();
}
}
}
public enum PluginType
{
Binary,
Script
}
}