Skip to content

Commit

Permalink
feat: Add plugins management
Browse files Browse the repository at this point in the history
PluginsProvider provides all plugins in specified directory
  • Loading branch information
sandre58 committed May 16, 2024
1 parent 14cedbb commit 47d23e5
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/MyNet.Utilities/Plugins/PluginLoadContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Stéphane ANDRE. All Right Reserved.
// See the LICENSE file in the project root for more information.

using System.Reflection;
using System.Runtime.Loader;

namespace MyNet.Utilities.Plugins
{
internal class PluginLoadContext(string pluginPath) : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver = new(pluginPath);

protected override Assembly? Load(AssemblyName assemblyName)
{
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
}

protected override nint LoadUnmanagedDll(string unmanagedDllName)
{
var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
return libraryPath != null ? LoadUnmanagedDllFromPath(libraryPath) : nint.Zero;
}
}
}
64 changes: 64 additions & 0 deletions src/MyNet.Utilities/Plugins/PluginService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Stéphane ANDRE. All Right Reserved.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace MyNet.Utilities.Plugins
{
public static class PluginService
{
public static IEnumerable<Type> GetTypes<T>(string pluginsDirectory)
{
if (!Directory.Exists(pluginsDirectory)) return [];

var result = new List<Type>();

foreach (var item in new DirectoryInfo(pluginsDirectory).GetDirectories())
{
var dllPath = Path.Combine(item.FullName, $"{item.Name}.dll");

if (File.Exists(dllPath))
{
var plugin = LoadAssemblyFromDll(dllPath);

if (plugin is not null)
result.AddRange(plugin.GetTypes().Where(x => x.IsAssignableTo(typeof(T))));
}
}

return result;
}

public static Type? GetType<T>(string pluginPath)
{
if (string.IsNullOrEmpty(pluginPath)) return default;

var plugin = LoadAssemblyFromDll(pluginPath);

return plugin is null ? null : Array.Find(plugin.GetTypes(), x => x.IsAssignableTo(typeof(T)));
}

public static T? CreateInstance<T>(string pluginPath, params object[] constructorParameters)
{
if (string.IsNullOrEmpty(pluginPath)) return default;

var plugin = GetType<T>(pluginPath);

return plugin is null ? default : (T?)Activator.CreateInstance(plugin, constructorParameters);
}

private static Assembly? LoadAssemblyFromDll(string dllPath, string? assemblyName = null)
{
if (!File.Exists(dllPath)) return null;

var finalAssemblyName = assemblyName ?? Path.GetFileNameWithoutExtension(dllPath);

var loadContext = new PluginLoadContext(dllPath);
return loadContext.LoadFromAssemblyName(new AssemblyName(finalAssemblyName));
}
}
}
44 changes: 44 additions & 0 deletions src/MyNet.Utilities/Plugins/PluginsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Stéphane ANDRE. All Right Reserved.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using MyNet.Utilities.Caching;

namespace MyNet.Utilities.Plugins
{
public class PluginsProvider(string root)
{
private readonly CacheStorage<Type, List<Type>> _cache = new();

public string Root { get; private set; } = root;

public List<Type> GetTypes<T>()
{
var types = _cache.Get(typeof(T));

if (types is null)
{
types = PluginService.GetTypes<T>(Root).ToList();
_cache.Add(typeof(T), types);
}

return types;
}

public Type? Get<T>(string? assemblyName = null)
{
var types = GetTypes<T>();

return !string.IsNullOrEmpty(assemblyName) ? types.Find(x => x.Assembly.GetName().Name == assemblyName) : types.FirstOrDefault();
}

public T? Create<T>(string? assemblyName = null, params object[] constructorParameters)
{
var type = Get<T>(assemblyName);

return type is null ? default : (T?)Activator.CreateInstance(type, constructorParameters);
}
}
}

0 comments on commit 47d23e5

Please sign in to comment.