Restructure solution layout by module
This commit is contained in:
172
src/__Libraries/StellaOps.Plugin/PluginContracts.cs
Normal file
172
src/__Libraries/StellaOps.Plugin/PluginContracts.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using StellaOps.Plugin.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Plugin;
|
||||
|
||||
public interface IAvailabilityPlugin
|
||||
{
|
||||
string Name { get; }
|
||||
bool IsAvailable(IServiceProvider services);
|
||||
}
|
||||
|
||||
public interface IFeedConnector
|
||||
{
|
||||
string SourceName { get; }
|
||||
Task FetchAsync(IServiceProvider services, CancellationToken cancellationToken);
|
||||
Task ParseAsync(IServiceProvider services, CancellationToken cancellationToken);
|
||||
Task MapAsync(IServiceProvider services, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public interface IFeedExporter
|
||||
{
|
||||
string Name { get; }
|
||||
Task ExportAsync(IServiceProvider services, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public interface IConnectorPlugin : IAvailabilityPlugin
|
||||
{
|
||||
IFeedConnector Create(IServiceProvider services);
|
||||
}
|
||||
|
||||
public interface IExporterPlugin : IAvailabilityPlugin
|
||||
{
|
||||
IFeedExporter Create(IServiceProvider services);
|
||||
}
|
||||
|
||||
public sealed class PluginCatalog
|
||||
{
|
||||
private readonly List<Assembly> _assemblies = new();
|
||||
private readonly HashSet<string> _assemblyLocations = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public PluginCatalog AddAssembly(Assembly assembly)
|
||||
{
|
||||
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
|
||||
if (_assemblies.Contains(assembly))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
_assemblies.Add(assembly);
|
||||
if (!string.IsNullOrWhiteSpace(assembly.Location))
|
||||
{
|
||||
_assemblyLocations.Add(Path.GetFullPath(assembly.Location));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public PluginCatalog AddFromDirectory(string directory, string searchPattern = "StellaOps.Concelier.*.dll")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directory)) throw new ArgumentException("Directory is required", nameof(directory));
|
||||
|
||||
var fullDirectory = Path.GetFullPath(directory);
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
PluginsDirectory = fullDirectory,
|
||||
EnsureDirectoryExists = false,
|
||||
RecursiveSearch = false,
|
||||
};
|
||||
options.SearchPatterns.Add(searchPattern);
|
||||
|
||||
var result = PluginHost.LoadPlugins(options);
|
||||
|
||||
foreach (var plugin in result.Plugins)
|
||||
{
|
||||
AddAssembly(plugin.Assembly);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IReadOnlyList<IConnectorPlugin> GetConnectorPlugins() => PluginLoader.LoadPlugins<IConnectorPlugin>(_assemblies);
|
||||
|
||||
public IReadOnlyList<IExporterPlugin> GetExporterPlugins() => PluginLoader.LoadPlugins<IExporterPlugin>(_assemblies);
|
||||
|
||||
public IReadOnlyList<IConnectorPlugin> GetAvailableConnectorPlugins(IServiceProvider services)
|
||||
=> FilterAvailable(GetConnectorPlugins(), services);
|
||||
|
||||
public IReadOnlyList<IExporterPlugin> GetAvailableExporterPlugins(IServiceProvider services)
|
||||
=> FilterAvailable(GetExporterPlugins(), services);
|
||||
|
||||
private static IReadOnlyList<TPlugin> FilterAvailable<TPlugin>(IEnumerable<TPlugin> plugins, IServiceProvider services)
|
||||
where TPlugin : IAvailabilityPlugin
|
||||
{
|
||||
var list = new List<TPlugin>();
|
||||
foreach (var plugin in plugins)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (plugin.IsAvailable(services))
|
||||
{
|
||||
list.Add(plugin);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Treat exceptions as plugin not available.
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PluginLoader
|
||||
{
|
||||
public static IReadOnlyList<TPlugin> LoadPlugins<TPlugin>(IEnumerable<Assembly> assemblies)
|
||||
where TPlugin : class
|
||||
{
|
||||
if (assemblies == null) throw new ArgumentNullException(nameof(assemblies));
|
||||
|
||||
var plugins = new List<TPlugin>();
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
foreach (var candidate in SafeGetTypes(assembly))
|
||||
{
|
||||
if (candidate.IsAbstract || candidate.IsInterface)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!typeof(TPlugin).IsAssignableFrom(candidate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Activator.CreateInstance(candidate) is not TPlugin plugin)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = candidate.FullName ?? candidate.Name;
|
||||
if (key is null || !seen.Add(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
plugins.Add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> SafeGetTypes(Assembly assembly)
|
||||
{
|
||||
try
|
||||
{
|
||||
return assembly.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
return ex.Types.Where(t => t is not null)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user