pschelpdesk/Vendor/Prise/AssemblyScanning/DefaultAssemblyScanner.cs
2024-11-11 19:08:09 +01:00

100 lines
4.5 KiB
C#

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<IDisposable> disposables;
protected Func<string, IMetadataLoadContext> metadataLoadContextFactory;
protected IDirectoryTraverser directoryTraverser;
public DefaultAssemblyScanner(Func<string, IMetadataLoadContext> metadataLoadContextFactory, Func<IDirectoryTraverser> directoryTraverser)
{
this.disposables = new List<IDisposable>();
this.metadataLoadContextFactory = metadataLoadContextFactory.ThrowIfNull(nameof(metadataLoadContextFactory));
this.directoryTraverser = directoryTraverser.ThrowIfNull(nameof(directoryTraverser))();
}
public virtual Task<IEnumerable<AssemblyScanResult>> 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<string> { "*.dll" };
var results = new List<AssemblyScanResult>();
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<string> ExcludeRuntimesFolder(IEnumerable<string> files) => files.Where(f => !f.Contains($"{Path.DirectorySeparatorChar}runtimes{Path.DirectorySeparatorChar}"));
private IEnumerable<Type> 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);
}
}
}