11 using System.Reflection;
12 using System.Reflection.Metadata;
13 using System.Reflection.PortableExecutable;
23 public ILogger?
AppLogger {
get;
internal set; } =
null;
45 List<IPlugin> plugins =
new List<IPlugin>();
46 foreach (var pluginList
in LoadedAssemblies.Select(x => x.Value.Select(y => y.Value))) {
47 plugins.AddRange(pluginList);
57 internal Dictionary<Assembly, Dictionary<Type, IPlugin>>
LoadedAssemblies {
get; } =
new Dictionary<Assembly, Dictionary<Type, IPlugin>>();
59 internal List<PluginDelegator>
PluginDelegators {
get; } =
new List<PluginDelegator>();
75 AppLogger?.Information($
"Attempting to load { pluginFile.FullName }...");
76 if (!pluginFile.Exists) {
throw new FileNotFoundException(
"The file does not exist.", pluginFile.FullName); }
79 AppLogger?.Information(
"Loading assembly...");
80 var assembly = Assembly.LoadFile(pluginFile.FullName);
82 List<Type> pluginTypes;
87 AppLogger?.Information($
"Found { pluginTypes.Count } plugins in assembly...");
88 foreach (var pluginType
in pluginTypes) {
100 using (var fStream = pluginFile.OpenRead())
101 using (var peReader =
new PEReader(fStream)) {
102 if (!peReader.HasMetadata) {
return false; }
104 var metaDataReader = peReader.GetMetadataReader();
105 return metaDataReader.IsAssembly;
107 }
catch (Exception) {
return false; }
117 pluginTypes =
new List<Type>();
119 foreach (var type
in assembly.GetTypes()) {
120 var isSubclass = type.IsSubclassOf(typeof(
IPlugin)) || type.IsSubclassOf(typeof(
Plugin));
123 if (isSubclass && attribute is not
null) {
124 pluginTypes.Add(type);
128 return pluginTypes.Count > 0;
138 AppLogger?.Debug($
"Plugin assembly seems to be new; registering { assembly.GetName().FullName } for the first time!");
141 AppLogger?.Error($
"The plugin { type.Name } has already been loaded as a plugin! Silently ignoring...");
146 var plugin = Activator.CreateInstance(type) as
IPlugin;
147 if (plugin is
null) {
156 plugin.OnLoad(pluginDelegator);
159 AppLogger?.Information($
"Loaded plugin { plugin.PluginName } { plugin.PluginVersion.ToString() }");
160 }
catch (Exception ex) {
161 AppLogger?.Error(
"Failed to load plugin from assembly!");
162 AppLogger?.Error($
"Error: { ex.Message }");
164 AppLogger?.Warning(
"Deregisterung plugin...");
174 var pluginAssembly =
LoadedAssemblies?.FirstOrDefault(a => a.Key == assembly);
175 if (pluginAssembly is
null) {
return; }
177 pluginAssembly?.Value.Remove(type);
182 if (pluginAssembly?.Value.Count == 0) {
188 foreach (var plugin
in Plugins) {
189 plugin.OnConfigLoaded();
194 foreach (var plugin
in Plugins) {
195 plugin.OnConfigChanged(e);
200 AppLogger?.Warning(
"Failed to resolve assembly {argname}", args.Name);
201 var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies()?.Where(x => x.FullName == args.RequestingAssembly?.FullName)?.FirstOrDefault();
202 if (loadedAssembly is not
null) {
203 return loadedAssembly;
206 var folderPath = Path.GetDirectoryName(args.RequestingAssembly?.Location);
207 var asmName =
new AssemblyName(args.Name);
208 if (folderPath is
null || asmName.Name is
null) {
209 AppLogger?.Error(
"Could not find assembly in {location}", folderPath);
213 var rawPath = Path.Combine(folderPath, asmName.Name);
214 var asmPath = rawPath +
".dll";
216 if (!File.Exists(asmPath)) {
217 AppLogger?.Warning(
"Could not find {asmPath}! Is it an executable?", asmPath);
218 asmPath = rawPath +
".exe";
219 if (!File.Exists(asmPath)) {
220 AppLogger?.Warning(
"Could not find {asmPath}!", asmPath);
225 return Assembly.LoadFrom(asmPath);
235 Plugins.ForEach(p => list.AddRange(p.PluginCommands));
Event arguments for when a configuration has changed.
Plugin attribute, contains metadata about a given plugin.
Exception class that is thrown when an attempt is made to load a plugin which is not in an assembly.
Exception class that is thrown when a plugin failed to load.
Allows delegating topics and command execution requests from plugins through Hermod to other plugins.
An abstract class for plugins with all the main features already implemented.
Handles the loading, unloading, and general management of plugins.
void ConfigManager_ConfigLoaded(object? sender, ConfigLoadedEventArgs e)
void DeregisterPlugin(Assembly assembly, Type type)
List< IPlugin > Plugins
Gets a list of all loaded IPlugin instances.
bool ContainsPlugins(Assembly assembly, out List< Type > pluginTypes)
Gets a value indicating whether or not a given assembly contains members inheriting from IPlugin.
void ConfigManager_ConfigChanged(object? sender, ConfigChangedEventArgs e)
void RegisterPlugin(ref Assembly assembly, Type type)
Internally registers an IPlugin class and calls the IPlugin.OnLoad(Serilog.ILogger) method once loade...
Dictionary< Assembly, Dictionary< Type, IPlugin > > LoadedAssemblies
A Dictionary<Assembly, List<IPlugin>> containing all loaded assemblies and plugins contained within.
bool IsAssembly(FileInfo pluginFile)
Gets a value indicating whether or not a give file is a valid assembly or not.
List< PluginDelegator > PluginDelegators
static ? PluginRegistry _instance
IPlugin? LastRegisteredPlugin
Gets or sets the last IPlugin to be registered.
Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs args)
void LoadPlugin(FileInfo pluginFile)
Loads one or plugins from an Assembly on disk.
static PluginRegistry Instance
Gets the current instance of this object.
List< ICommand > GetAllCommands()
Gets a list containing all ICommand instances known to the application at the current time.
List< ICommand >? BuiltInCommands
Basic contract between Hermod and any plugins.