using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Prise.Activation; using Prise.AssemblyLoading; using Prise.AssemblyScanning; using Prise.Proxy; using Prise.Utils; namespace Prise { public class DefaultPluginLoader : IPluginLoader { private readonly IAssemblyScanner assemblyScanner; private readonly IPluginTypeSelector pluginTypeSelector; private readonly IAssemblyLoader assemblyLoader; private readonly IParameterConverter parameterConverter; private readonly IResultConverter resultConverter; private readonly IPluginActivator pluginActivator; protected readonly ConcurrentBag pluginContexts; public DefaultPluginLoader( IAssemblyScanner assemblyScanner, IPluginTypeSelector pluginTypeSelector, IAssemblyLoader assemblyLoader, IParameterConverter parameterConverter, IResultConverter resultConverter, IPluginActivator pluginActivator) { this.assemblyScanner = assemblyScanner; this.pluginTypeSelector = pluginTypeSelector; this.assemblyLoader = assemblyLoader; this.parameterConverter = parameterConverter; this.resultConverter = resultConverter; this.pluginActivator = pluginActivator; this.pluginContexts = new ConcurrentBag(); } public async Task FindPlugin(string pathToPlugin) { return (await this.FindPlugins(pathToPlugin)) .FirstOrDefault(); } public async Task FindPlugin(string pathToPlugins, string plugin) { return (await this.FindPlugins(pathToPlugins)) .FirstOrDefault(p => p.AssemblyPath.Split(Path.DirectorySeparatorChar).Last().Equals(plugin)); } public Task> FindPlugins(string pathToPlugins) { return this.assemblyScanner.Scan(new AssemblyScannerOptions { StartingPath = pathToPlugins, PluginType = typeof(T) }); } public async Task LoadPlugin(AssemblyScanResult scanResult, string hostFramework = null, Action configureLoadContext = null) { hostFramework = hostFramework.ValueOrDefault(HostFrameworkUtils.GetHostframeworkFromHost()); var pathToAssembly = Path.Combine(scanResult.AssemblyPath, scanResult.AssemblyName); var pluginLoadContext = PluginLoadContext.DefaultPluginLoadContext(pathToAssembly, typeof(T), hostFramework); // This allows the loading of netstandard plugins pluginLoadContext.IgnorePlatformInconsistencies = true; configureLoadContext?.Invoke(pluginLoadContext); this.pluginContexts.Add(pluginLoadContext); var pluginAssembly = await this.assemblyLoader.Load(pluginLoadContext); var pluginTypes = this.pluginTypeSelector.SelectPluginTypes(pluginAssembly); var pluginType = pluginTypes.FirstOrDefault(p => p.Name.Equals(scanResult.PluginType.Name)); if (pluginType == null) throw new PluginLoadException($"Did not found any plugins to load from {nameof(AssemblyScanResult)} {scanResult.AssemblyPath} {scanResult.AssemblyName}"); return await this.pluginActivator.ActivatePlugin(new DefaultPluginActivationOptions { PluginType = pluginType, PluginAssembly = pluginAssembly, ParameterConverter = this.parameterConverter, ResultConverter = this.resultConverter, HostServices = pluginLoadContext.HostServices }); } public async Task> LoadPlugins(AssemblyScanResult scanResult, string hostFramework = null, Action configureLoadContext = null) { var plugins = new List(); #if SUPPORTS_ASYNC_STREAMS await foreach (var plugin in this.LoadPluginsAsAsyncEnumerable(scanResult, hostFramework, configureLoadContext)) #else foreach(var plugin in await this.LoadPluginsAsAsyncEnumerable(scanResult, hostFramework, configureLoadContext)) #endif plugins.Add(plugin); return plugins; } #if SUPPORTS_ASYNC_STREAMS public async IAsyncEnumerable LoadPluginsAsAsyncEnumerable(AssemblyScanResult scanResult, string hostFramework = null, Action configureLoadContext = null) { #else public async Task> LoadPluginsAsAsyncEnumerable(AssemblyScanResult scanResult, string hostFramework = null, Action configureLoadContext = null) { #endif hostFramework = hostFramework.ValueOrDefault(HostFrameworkUtils.GetHostframeworkFromHost()); var pathToAssembly = Path.Combine(scanResult.AssemblyPath, scanResult.AssemblyName); var pluginLoadContext = PluginLoadContext.DefaultPluginLoadContext(pathToAssembly, typeof(T), hostFramework); // This allows the loading of netstandard plugins pluginLoadContext.IgnorePlatformInconsistencies = true; configureLoadContext?.Invoke(pluginLoadContext); var pluginAssembly = await this.assemblyLoader.Load(pluginLoadContext); this.pluginContexts.Add(pluginLoadContext); var pluginTypes = this.pluginTypeSelector.SelectPluginTypes(pluginAssembly); #if SUPPORTS_ASYNC_STREAMS foreach (var pluginType in pluginTypes) yield return await this.pluginActivator.ActivatePlugin(new DefaultPluginActivationOptions { PluginType = pluginType, PluginAssembly = pluginAssembly, ParameterConverter = this.parameterConverter, ResultConverter = this.resultConverter, HostServices = pluginLoadContext.HostServices }); #else var plugins = new List(); foreach (var pluginType in pluginTypes) plugins.Add( await this.pluginActivator.ActivatePlugin(new DefaultPluginActivationOptions { PluginType = pluginType, PluginAssembly = pluginAssembly, ParameterConverter = this.parameterConverter, ResultConverter = this.resultConverter, HostServices = pluginLoadContext.HostServices })); return plugins; #endif } public void UnloadAll() { foreach (var context in this.pluginContexts) this.assemblyLoader.Unload(context); this.pluginContexts.Clear(); } protected bool disposed = false; protected virtual void Dispose(bool disposing) { if (!this.disposed && disposing) UnloadAll(); this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }