From aca3da09b182c3a5ea32d87cd3f0dae2232ce32b Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Thu, 1 Apr 2021 21:11:32 +0200 Subject: [PATCH] refactor: new code style in PluginManager.cs --- Dalamud/Plugin/PluginManager.cs | 194 +++++++++++++++++++++++--------- 1 file changed, 140 insertions(+), 54 deletions(-) diff --git a/Dalamud/Plugin/PluginManager.cs b/Dalamud/Plugin/PluginManager.cs index fa56e8a4b..4b6fe88c1 100644 --- a/Dalamud/Plugin/PluginManager.cs +++ b/Dalamud/Plugin/PluginManager.cs @@ -1,18 +1,25 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Dynamic; using System.IO; using System.Linq; using System.Reflection; + using Dalamud.Configuration; using Newtonsoft.Json; using Serilog; namespace Dalamud.Plugin { - internal class PluginManager { - public static int DALAMUD_API_LEVEL = 2; + /// + /// Class responsible for loading and unloading plugins. + /// + internal class PluginManager + { + /// + /// The current Dalamud API level, used to handle breaking changes. Only plugins with this level will be loaded. + /// + public const int DalamudApiLevel = 2; private readonly Dalamud dalamud; private readonly string pluginDirectory; @@ -22,15 +29,23 @@ namespace Dalamud.Plugin private readonly Type interfaceType = typeof(IDalamudPlugin); - public readonly List<(IDalamudPlugin Plugin, PluginDefinition Definition, DalamudPluginInterface PluginInterface, bool IsRaw)> Plugins = new List<(IDalamudPlugin plugin, PluginDefinition def, DalamudPluginInterface PluginInterface, bool IsRaw)>(); - - public List<(string SourcePluginName, string SubPluginName, Action SubAction)> IpcSubscriptions = new List<(string SourcePluginName, string SubPluginName, Action SubAction)>(); - - public PluginManager(Dalamud dalamud, string pluginDirectory, string devPluginDirectory) { + /// + /// Initializes a new instance of the class. + /// + /// The instance to load plugins with. + /// The directory for regular plugins. + /// The directory for dev plugins. + public PluginManager(Dalamud dalamud, string pluginDirectory, string devPluginDirectory) + { this.dalamud = dalamud; this.pluginDirectory = pluginDirectory; this.devPluginDirectory = devPluginDirectory; + this.Plugins = + new List<(IDalamudPlugin Plugin, PluginDefinition Definition, DalamudPluginInterface PluginInterface, + bool IsRaw)>(); + this.IpcSubscriptions = new List<(string SourcePluginName, string SubPluginName, Action SubAction)>(); + this.pluginConfigs = new PluginConfigurations(Path.Combine(Path.GetDirectoryName(dalamud.StartInfo.ConfigurationPath), "pluginConfigs")); // Try to load missing assemblies from the local directory of the requesting assembly @@ -38,49 +53,78 @@ namespace Dalamud.Plugin // This handler should only be invoked on things that fail regular lookups, but it *is* global to this appdomain AppDomain.CurrentDomain.AssemblyResolve += (object source, ResolveEventArgs e) => { - try { + try + { Log.Debug($"Resolving missing assembly {e.Name}"); + // This looks weird but I'm pretty sure it's actually correct. Pretty sure. Probably. - var assemblyPath = Path.Combine(Path.GetDirectoryName(e.RequestingAssembly.Location), - new AssemblyName(e.Name).Name + ".dll"); - if (!File.Exists(assemblyPath)) { + var assemblyPath = Path.Combine( + Path.GetDirectoryName(e.RequestingAssembly.Location), + new AssemblyName(e.Name).Name + ".dll"); + + if (!File.Exists(assemblyPath)) + { Log.Error($"Assembly not found at {assemblyPath}"); return null; } return Assembly.LoadFrom(assemblyPath); - } catch(Exception ex) { + } + catch (Exception ex) + { Log.Error(ex, "Could not load assembly " + e.Name); return null; } }; } - public void UnloadPlugins() { + /// + /// Gets a list of all loaded plugins. + /// + public List<(IDalamudPlugin Plugin, PluginDefinition Definition, DalamudPluginInterface PluginInterface, bool IsRaw)> Plugins { get; private set; } + + /// + /// Gets a list of all IPC subscriptions. + /// + public List<(string SourcePluginName, string SubPluginName, Action SubAction)> IpcSubscriptions { get; private set; } + + /// + /// Unload all plugins. + /// + public void UnloadPlugins() + { if (this.Plugins == null) return; - for (var i = 0; i < this.Plugins.Count; i++) { + for (var i = 0; i < this.Plugins.Count; i++) + { this.Plugins[i].Plugin.Dispose(); } this.Plugins.Clear(); } - public void LoadPlugins() { - var loadDirectories = new List<(DirectoryInfo dirInfo, bool isRaw)> { + /// + /// Load all regular and dev plugins. + /// + public void LoadPlugins() + { + var loadDirectories = new List<(DirectoryInfo dirInfo, bool isRaw)> + { (new DirectoryInfo(this.pluginDirectory), false), - (new DirectoryInfo(this.devPluginDirectory), true) + (new DirectoryInfo(this.devPluginDirectory), true), }; var pluginDefs = new List<(FileInfo dllFile, PluginDefinition definition, bool isRaw)>(); - foreach (var (dirInfo, isRaw) in loadDirectories) { + foreach (var (dirInfo, isRaw) in loadDirectories) + { if (!dirInfo.Exists) continue; var pluginDlls = dirInfo.GetFiles("*.dll", SearchOption.AllDirectories).Where(x => x.Extension == ".dll"); // Preload definitions to be able to determine load order - foreach (var dllFile in pluginDlls) { + foreach (var dllFile in pluginDlls) + { var defJson = new FileInfo(Path.Combine(dllFile.Directory.FullName, $"{Path.GetFileNameWithoutExtension(dllFile.Name)}.json")); PluginDefinition def = null; if (defJson.Exists) @@ -91,21 +135,27 @@ namespace Dalamud.Plugin // Sort for load order - unloaded definitions have default priority of 0 pluginDefs.Sort( - (info1, info2) => { + (info1, info2) => + { var prio1 = info1.definition?.LoadPriority ?? 0; var prio2 = info2.definition?.LoadPriority ?? 0; return prio2.CompareTo(prio1); }); // Pass preloaded definitions to LoadPluginFromAssembly, because we already loaded them anyways - foreach (var (dllFile, definition, isRaw) in pluginDefs) { - try { - LoadPluginFromAssembly(dllFile, isRaw, PluginLoadReason.Boot, true, definition); + foreach (var (dllFile, definition, isRaw) in pluginDefs) + { + try + { + this.LoadPluginFromAssembly(dllFile, isRaw, PluginLoadReason.Boot, true, definition); } - catch (Exception ex) { + catch (Exception ex) + { Log.Error(ex, $"Plugin load for {dllFile.FullName} failed."); - if (ex is ReflectionTypeLoadException typeLoadException) { - foreach (var exception in typeLoadException.LoaderExceptions) { + if (ex is ReflectionTypeLoadException typeLoadException) + { + foreach (var exception in typeLoadException.LoaderExceptions) + { Log.Error(exception, "LoaderException:"); } } @@ -113,7 +163,12 @@ namespace Dalamud.Plugin } } - public void DisablePlugin(PluginDefinition definition) { + /// + /// Disable/unload a single plugin. + /// + /// The plugin definition of the plugin to be disabled/unloaded. + public void DisablePlugin(PluginDefinition definition) + { var thisPlugin = this.Plugins.Where(x => x.Definition != null) .First(x => x.Definition.InternalName == definition.InternalName); @@ -121,11 +176,15 @@ namespace Dalamud.Plugin // Need to do it with Open so the file handle gets closed immediately // TODO: Don't use the ".disabled" crap, do it in a config - try { + try + { File.Open(Path.Combine(outputDir.FullName, ".disabled"), FileMode.Create).Close(); - } catch (Exception ex) { + } + catch (Exception ex) + { Log.Error(ex, "Could not create the .disabled file, disabling all versions..."); - foreach (var version in outputDir.Parent.GetDirectories()) { + foreach (var version in outputDir.Parent.GetDirectories()) + { if (!File.Exists(Path.Combine(version.FullName, ".disabled"))) File.Open(Path.Combine(version.FullName, ".disabled"), FileMode.Create).Close(); } @@ -136,19 +195,32 @@ namespace Dalamud.Plugin this.Plugins.Remove(thisPlugin); } - public bool LoadPluginFromAssembly(FileInfo dllFile, bool isRaw, PluginLoadReason reason, bool preloaded = false, PluginDefinition preloadedDef = null) { + /// + /// Load a plugin from an assembly. + /// + /// The associated with the main assembly of this plugin. + /// Whether or not the plugin is a dev plugin. + /// The reason this plugin was loaded. + /// Whether or not to skip loading a definition from a file path. + /// The already loaded definition, when is set to true. + /// Whether or not the plugin was loaded successfully. + public bool LoadPluginFromAssembly(FileInfo dllFile, bool isRaw, PluginLoadReason reason, bool preloaded = false, PluginDefinition preloadedDef = null) + { Log.Information("Loading plugin at {0}", dllFile.Directory.FullName); // If this entire folder has been marked as a disabled plugin, don't even try to load anything var disabledFile = new FileInfo(Path.Combine(dllFile.Directory.FullName, ".disabled")); - if (disabledFile.Exists && !isRaw) // should raw/dev plugins really not respect this? + + // should raw/dev plugins really not respect this? + if (disabledFile.Exists && !isRaw) { Log.Information("Plugin {0} is disabled.", dllFile.FullName); return false; } var testingFile = new FileInfo(Path.Combine(dllFile.Directory.FullName, ".testing")); - if (testingFile.Exists && !this.dalamud.Configuration.DoPluginTest) { + if (testingFile.Exists && !this.dalamud.Configuration.DoPluginTest) + { Log.Information("Plugin {0} was testing, but testing is disabled.", dllFile.FullName); return false; } @@ -156,39 +228,45 @@ namespace Dalamud.Plugin PluginDefinition pluginDef = null; // Preloaded - if (preloaded) { + if (preloaded) + { if (preloadedDef == null && !isRaw) { Log.Information("Plugin DLL {0} has no definition.", dllFile.FullName); return false; } - if (preloadedDef != null && - preloadedDef.ApplicableVersion != this.dalamud.StartInfo.GameVersion && + + if (preloadedDef != null && + preloadedDef.ApplicableVersion != this.dalamud.StartInfo.GameVersion && preloadedDef.ApplicableVersion != "any") { Log.Information("Plugin {0} has not applicable version.", dllFile.FullName); return false; } + pluginDef = preloadedDef; - } else { + } + else + { // read the plugin def if present - again, fail before actually trying to load the dll if there is a problem var defJsonFile = new FileInfo(Path.Combine(dllFile.Directory.FullName, $"{Path.GetFileNameWithoutExtension(dllFile.Name)}.json")); - + // load the definition if it exists, even for raw/developer plugins if (defJsonFile.Exists) { Log.Information("Loading definition for plugin DLL {0}", dllFile.FullName); - + pluginDef = JsonConvert.DeserializeObject( File.ReadAllText(defJsonFile.FullName)); - + if (pluginDef.ApplicableVersion != this.dalamud.StartInfo.GameVersion && pluginDef.ApplicableVersion != "any") { Log.Information("Plugin {0} has not applicable version.", dllFile.FullName); return false; } } + // but developer plugins don't require one to load else if (!isRaw) { @@ -196,11 +274,10 @@ namespace Dalamud.Plugin return false; } } - + // TODO: given that it exists, the pluginDef's InternalName should probably be used // as the actual assembly to load // But plugins should also probably be loaded by directory and not by looking for every dll - Log.Information("Loading assembly at {0}", dllFile); // Assembly.Load() by name here will not load multiple versions with the same name, in the case of updates @@ -215,7 +292,7 @@ namespace Dalamud.Plugin continue; } - if (type.GetInterface(interfaceType.FullName) != null) + if (type.GetInterface(this.interfaceType.FullName) != null) { if (this.Plugins.Any(x => x.Plugin.GetType().Assembly.GetName().Name == type.Assembly.GetName().Name)) { @@ -228,33 +305,38 @@ namespace Dalamud.Plugin var plugin = (IDalamudPlugin)Activator.CreateInstance(type); // this happens for raw plugins that don't specify a PluginDefinition - just generate a dummy one to avoid crashes anywhere - pluginDef ??= new PluginDefinition{ + pluginDef ??= new PluginDefinition + { Author = "developer", Name = plugin.Name, InternalName = Path.GetFileNameWithoutExtension(dllFile.Name), AssemblyVersion = plugin.GetType().Assembly.GetName().Version.ToString(), - Description = "", + Description = string.Empty, ApplicableVersion = "any", IsHide = false, - DalamudApiLevel = DALAMUD_API_LEVEL + DalamudApiLevel = DalamudApiLevel, }; - if (pluginDef.InternalName == "PingPlugin" && pluginDef.AssemblyVersion == "1.11.0.0") { + if (pluginDef.InternalName == "PingPlugin" && pluginDef.AssemblyVersion == "1.11.0.0") + { Log.Error("Banned PingPlugin"); return false; } - if (pluginDef.InternalName == "FPSPlugin" && pluginDef.AssemblyVersion == "1.4.2.0") { + if (pluginDef.InternalName == "FPSPlugin" && pluginDef.AssemblyVersion == "1.4.2.0") + { Log.Error("Banned PingPlugin"); return false; } - if (pluginDef.InternalName == "SonarPlugin" && pluginDef.AssemblyVersion == "0.1.3.1") { + if (pluginDef.InternalName == "SonarPlugin" && pluginDef.AssemblyVersion == "0.1.3.1") + { Log.Error("Banned SonarPlugin"); return false; } - if (pluginDef.DalamudApiLevel < DALAMUD_API_LEVEL) { + if (pluginDef.DalamudApiLevel < DalamudApiLevel) + { Log.Error("Incompatible API level: {0}", dllFile.FullName); return false; } @@ -276,9 +358,13 @@ namespace Dalamud.Plugin return false; } - public void ReloadPlugins() { - UnloadPlugins(); - LoadPlugins(); + /// + /// Unload and reload all plugins. + /// + public void ReloadPlugins() + { + this.UnloadPlugins(); + this.LoadPlugins(); } } }