using System; using System.IO; using System.Collections.Generic; using System.Reflection; using SharedLibraryCore.Interfaces; using System.Linq; using SharedLibraryCore; using IW4MAdmin.Application.API.Master; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SharedLibraryCore.Configuration; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace IW4MAdmin.Application.Misc { /// <summary> /// implementation of IPluginImporter /// discovers plugins and script plugins /// </summary> public class PluginImporter : IPluginImporter { private IEnumerable<PluginSubscriptionContent> _pluginSubscription; private static readonly string PLUGIN_DIR = "Plugins"; private readonly ILogger _logger; private readonly IRemoteAssemblyHandler _remoteAssemblyHandler; private readonly IMasterApi _masterApi; private readonly ApplicationConfiguration _appConfig; public PluginImporter(ILogger<PluginImporter> logger, ApplicationConfiguration appConfig, IMasterApi masterApi, IRemoteAssemblyHandler remoteAssemblyHandler) { _logger = logger; _masterApi = masterApi; _remoteAssemblyHandler = remoteAssemblyHandler; _appConfig = appConfig; } /// <summary> /// discovers all the script plugins in the plugins dir /// </summary> /// <returns></returns> public IEnumerable<IPlugin> DiscoverScriptPlugins() { var pluginDir = $"{Utilities.OperatingDirectory}{PLUGIN_DIR}{Path.DirectorySeparatorChar}"; if (!Directory.Exists(pluginDir)) { return Enumerable.Empty<IPlugin>(); } var scriptPluginFiles = Directory.GetFiles(pluginDir, "*.js").AsEnumerable().Union(GetRemoteScripts()).ToList(); _logger.LogDebug("Discovered {count} potential script plugins", scriptPluginFiles.Count); return scriptPluginFiles.Select(fileName => { _logger.LogDebug("Discovered script plugin {fileName}", fileName); return new ScriptPlugin(_logger, fileName); }).ToList(); } /// <summary> /// discovers all the C# assembly plugins and commands /// </summary> /// <returns></returns> public (IEnumerable<Type>, IEnumerable<Type>, IEnumerable<Type>) DiscoverAssemblyPluginImplementations() { var pluginDir = $"{Utilities.OperatingDirectory}{PLUGIN_DIR}{Path.DirectorySeparatorChar}"; var pluginTypes = Enumerable.Empty<Type>(); var commandTypes = Enumerable.Empty<Type>(); var configurationTypes = Enumerable.Empty<Type>(); if (Directory.Exists(pluginDir)) { var dllFileNames = Directory.GetFiles(pluginDir, "*.dll"); _logger.LogDebug("Discovered {count} potential plugin assemblies", dllFileNames.Length); if (dllFileNames.Length > 0) { // we only want to load the most recent assembly in case of duplicates var assemblies = dllFileNames.Select(_name => Assembly.LoadFrom(_name)) .Union(GetRemoteAssemblies()) .GroupBy(_assembly => _assembly.FullName).Select(_assembly => _assembly.OrderByDescending(_assembly => _assembly.GetName().Version).First()); pluginTypes = assemblies .SelectMany(_asm => { try { return _asm.GetTypes(); } catch { return Enumerable.Empty<Type>(); } }) .Where(_assemblyType => _assemblyType.GetInterface(nameof(IPlugin), false) != null); _logger.LogDebug("Discovered {count} plugin implementations", pluginTypes.Count()); commandTypes = assemblies .SelectMany(_asm =>{ try { return _asm.GetTypes(); } catch { return Enumerable.Empty<Type>(); } }) .Where(_assemblyType => _assemblyType.IsClass && _assemblyType.BaseType == typeof(Command)); _logger.LogDebug("Discovered {count} plugin commands", commandTypes.Count()); configurationTypes = assemblies .SelectMany(asm => { try { return asm.GetTypes(); } catch { return Enumerable.Empty<Type>(); } }) .Where(asmType => asmType.IsClass && asmType.GetInterface(nameof(IBaseConfiguration), false) != null); _logger.LogDebug("Discovered {count} configuration implementations", configurationTypes.Count()); } } return (pluginTypes, commandTypes, configurationTypes); } private IEnumerable<Assembly> GetRemoteAssemblies() { try { if (_pluginSubscription == null) _pluginSubscription = _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result; return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription.Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray()); } catch (Exception ex) { _logger.LogWarning(ex, "Could not load remote assemblies"); return Enumerable.Empty<Assembly>(); } } private IEnumerable<string> GetRemoteScripts() { try { if (_pluginSubscription == null) _pluginSubscription = _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result; return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription.Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray()); } catch (Exception ex) { _logger.LogWarning(ex,"Could not load remote scripts"); return Enumerable.Empty<string>(); } } } public enum PluginType { Binary, Script } }