-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathPluginAssembly.cs
More file actions
157 lines (137 loc) · 5.54 KB
/
PluginAssembly.cs
File metadata and controls
157 lines (137 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using CommandLine;
using InEngine.Core.Exceptions;
using Microsoft.Extensions.Logging;
namespace InEngine.Core;
public class PluginAssembly
{
private ILogger Log { get; set; } = LogManager.GetLogger<PluginAssembly>();
public Assembly Assembly { get; set; }
public string Name => Assembly.GetName().Name;
public string Version => Assembly.GetName().Version.ToString();
public List<AbstractPlugin> Plugins { get; set; }
public static volatile bool IsAssemblyResolverRegistered;
public static readonly Mutex AssemblyResolverLock = new();
public PluginAssembly(Assembly assembly)
{
Assembly = assembly;
Plugins = Make<AbstractPlugin>();
}
public static PluginAssembly LoadFrom(string pluginName)
{
RegisterPluginAssemblyResolver();
var path = MakeFullPluginAssemblyPath(pluginName);
try
{
return new PluginAssembly(Assembly.LoadFrom(path));
}
catch (Exception exception)
{
const string message = $"Plugin not found";
LogManager.GetLogger<PluginAssembly>().LogError(exception, message);
throw new PluginNotFoundException(message, exception);
}
}
public static List<PluginAssembly> Load<T>(bool shouldLoadCorePlugin = true) where T : IPlugin
{
RegisterPluginAssemblyResolver();
var pluginList = new List<PluginAssembly>();
try
{
if (shouldLoadCorePlugin)
pluginList.Add(new PluginAssembly(Assembly.GetExecutingAssembly()));
}
catch (Exception exception)
{
const string message = "Could not load InEngine.Core plugin.";
LogManager.GetLogger<PluginAssembly>().LogError(exception, message);
throw new PluginNotFoundException(message, exception);
}
var assemblies = InEngineSettings
.Make()
.Plugins
.Select(x => Assembly.LoadFrom(Path.Combine(x.Value, $"{x.Key}.dll")));
foreach (var assembly in assemblies)
{
try
{
if (assembly.GetTypes().Any(x => x.IsClass && typeof(T).IsAssignableFrom(x) && !x.IsAbstract))
pluginList.Add(new PluginAssembly(assembly));
}
catch (Exception exception)
{
throw new PluginNotFoundException($"Could not load {assembly.GetName().Name} plugin.", exception);
}
}
if (!pluginList.Any())
throw new PluginNotFoundException("There are no plugins available.");
return pluginList.OrderBy(x => x.Name).ToList();
}
public static void RegisterPluginAssemblyResolver()
{
AssemblyResolverLock.WaitOne();
if (!IsAssemblyResolverRegistered)
{
IsAssemblyResolverRegistered = true;
AppDomain.CurrentDomain.AssemblyResolve += LoadPluginEventHandler;
}
AssemblyResolverLock.ReleaseMutex();
}
private static Assembly LoadPluginEventHandler(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name).Name;
if (assemblyName == null)
return null;
var pluginName = assemblyName[..^4];
var assemblyPath = MakeFullPluginAssemblyPath(pluginName);
return File.Exists(assemblyPath) ? Assembly.LoadFrom(assemblyPath) : null;
}
private static string MakeFullPluginAssemblyPath(string pluginName)
{
var plugins = InEngineSettings.Make().Plugins;
var isCorePlugin = Assembly.GetCallingAssembly().GetName().Name == pluginName;
if (!isCorePlugin && !plugins.ContainsKey(pluginName))
throw new PluginNotRegisteredException(pluginName);
return Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty,
isCorePlugin ? "" : plugins[pluginName],
$"{pluginName}.dll"
);
}
public List<T> Make<T>() where T : class, IPlugin
{
return Assembly
.GetTypes()
.Where(x => x.IsClass &&
typeof(T).IsAssignableFrom(x) &&
!x.IsAbstract &&
!string.IsNullOrWhiteSpace(x.FullName)
)
.Select(x => Assembly.CreateInstance(x.FullName) as T)
.ToList();
}
public AbstractCommand CreateCommandFromClass(string fullCommandName) =>
Assembly.CreateInstance(fullCommandName) as AbstractCommand;
public AbstractCommand CreateCommandFromVerb(string verbName)
{
var commandClassNames = new List<string>();
var optionsList = Make<AbstractPlugin>();
foreach (var options in optionsList)
foreach (var property in options.GetType().GetProperties())
foreach (var attribute in property.GetCustomAttributes(true))
if (attribute is VerbOptionAttribute optionAttribute && optionAttribute.LongName == verbName)
commandClassNames.Add(property.PropertyType.FullName);
var commandCount = commandClassNames.Count();
if (commandCount > 1)
throw new AmbiguousCommandException(verbName);
if (commandCount == 0)
throw new CommandNotFoundException(verbName);
return Assembly.CreateInstance(commandClassNames.First()) as AbstractCommand;
}
public Type GetCommandType(string commandClassName) => Assembly.GetType(commandClassName);
}