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();
}
}
}