using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Prise.Utils; namespace Prise.AssemblyScanning { public class DefaultAssemblyScanner : IAssemblyScanner { protected IList disposables; protected Func metadataLoadContextFactory; protected IDirectoryTraverser directoryTraverser; public DefaultAssemblyScanner(Func metadataLoadContextFactory, Func directoryTraverser) { this.disposables = new List(); this.metadataLoadContextFactory = metadataLoadContextFactory.ThrowIfNull(nameof(metadataLoadContextFactory)); this.directoryTraverser = directoryTraverser.ThrowIfNull(nameof(directoryTraverser))(); } public virtual Task> Scan(IAssemblyScannerOptions options) { if (options == null) throw new ArgumentNullException($"{typeof(IAssemblyScannerOptions).Name} {nameof(options)}"); var startingPath = options.StartingPath ?? throw new ArgumentException($"{nameof(options.StartingPath)}"); var typeToScan = options.PluginType ?? throw new ArgumentException($"{nameof(options.PluginType)}"); var fileTypes = options.FileTypes; if (!Path.IsPathRooted(startingPath)) throw new AssemblyScanningException($"startingPath {startingPath} is not rooted, this must be a absolute path!"); if (fileTypes == null) fileTypes = new List { "*.dll" }; var results = new List(); foreach (var directory in this.directoryTraverser.TraverseDirectories(startingPath)) { foreach (var assemblyFilePath in this.directoryTraverser.TraverseFiles(directory, fileTypes)) { foreach (var implementation in GetImplementationsOfTypeFromAssembly(typeToScan, assemblyFilePath)) if (implementation != null) { results.Add(new AssemblyScanResult { ContractType = typeToScan, AssemblyName = Path.GetFileName(assemblyFilePath), AssemblyPath = Path.GetDirectoryName(assemblyFilePath), PluginType = implementation }); Console.WriteLine($"Scanning File {assemblyFilePath}"); } } } return Task.FromResult(results.AsEnumerable()); } private IEnumerable ExcludeRuntimesFolder(IEnumerable files) => files.Where(f => !f.Contains($"{Path.DirectorySeparatorChar}runtimes{Path.DirectorySeparatorChar}")); private IEnumerable GetImplementationsOfTypeFromAssembly(Type type, string assemblyFullPath) { var context = this.metadataLoadContextFactory(assemblyFullPath); var assembly = context.LoadFromAssemblyName(Path.GetFileNameWithoutExtension(assemblyFullPath)); this.disposables.Add(context); return assembly.Types .Where(t => t.CustomAttributes .Any(c => c.AttributeType.Name == typeof(Prise.Plugin.PluginAttribute).Name && (c.NamedArguments.First(a => a.MemberName == "PluginType").TypedValue.Value as Type).Name == type.Name && (c.NamedArguments.First(a => a.MemberName == "PluginType").TypedValue.Value as Type).Namespace == type.Namespace)) .OrderBy(t => t.Name) .ToList(); } protected bool disposed = false; protected virtual void Dispose(bool disposing) { if (!this.disposed && disposing) { if (this.disposables != null && this.disposables.Any()) foreach (var disposable in this.disposables) disposable.Dispose(); GC.Collect(); // collects all unused memory GC.WaitForPendingFinalizers(); // wait until GC has finished its work } this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }