From 6b336964a79abd8bdf24b2554affdc0197d82bc0 Mon Sep 17 00:00:00 2001 From: xx-TheDoctor-xx Date: Thu, 14 May 2020 17:06:45 +0100 Subject: [PATCH] Project restructure --- PluginManager.Test/PluginManager.Test.csproj | 21 --- PluginManager.Test/PluginManager.cs | 38 ---- ...uginManager.csproj => PluginManager.csproj | 0 PluginManager/Manager/PluginManager.cs | 162 ------------------ TestPlugin/Class1.cs | 50 ------ TestPlugin/TestPlugin.csproj | 15 -- src/Manager/PluginClassManager.cs | 39 +++++ .../Manager/PluginDataManager.cs | 0 src/Manager/PluginDllManager.cs | 74 ++++++++ src/Manager/PluginManagerBase.cs | 112 ++++++++++++ {PluginManager => src}/Plugin/Plugin.cs | 37 +++- {PluginManager => src}/Plugin/PluginData.cs | 0 .../Plugin/PluginProcessData.cs | 0 src/Plugin/PluginResponseData.cs | 10 ++ {PluginManager => src}/PluginConfig.cs | 7 + 15 files changed, 272 insertions(+), 293 deletions(-) delete mode 100644 PluginManager.Test/PluginManager.Test.csproj delete mode 100644 PluginManager.Test/PluginManager.cs rename PluginManager/PluginManager.csproj => PluginManager.csproj (100%) delete mode 100644 PluginManager/Manager/PluginManager.cs delete mode 100644 TestPlugin/Class1.cs delete mode 100644 TestPlugin/TestPlugin.csproj create mode 100644 src/Manager/PluginClassManager.cs rename {PluginManager => src}/Manager/PluginDataManager.cs (100%) create mode 100644 src/Manager/PluginDllManager.cs create mode 100644 src/Manager/PluginManagerBase.cs rename {PluginManager => src}/Plugin/Plugin.cs (58%) rename {PluginManager => src}/Plugin/PluginData.cs (100%) rename {PluginManager => src}/Plugin/PluginProcessData.cs (100%) create mode 100644 src/Plugin/PluginResponseData.cs rename {PluginManager => src}/PluginConfig.cs (54%) diff --git a/PluginManager.Test/PluginManager.Test.csproj b/PluginManager.Test/PluginManager.Test.csproj deleted file mode 100644 index 5e0573b..0000000 --- a/PluginManager.Test/PluginManager.Test.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - netcoreapp3.1 - - false - - - - - - - - - - - - - - - diff --git a/PluginManager.Test/PluginManager.cs b/PluginManager.Test/PluginManager.cs deleted file mode 100644 index ec7b5af..0000000 --- a/PluginManager.Test/PluginManager.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Xunit; -using PluginManager.Manager; -using TestPlugin; - -namespace PluginManager.Test -{ - public class PDM : PluginDataManager - { - public override PluginData LoadData(string pluginDataFolder) - { - return null; - } - - public override void SaveData(string yes, PluginData data) - { - - } - } - - class PPD : PluginProcessData - { - - } - - public class PluginManagerTest - { - [Fact] - public void Test1() - { - PluginManager pluginManager = new PluginManager(); - PluginManagerConfig.PluginFolderPath = "Plugins"; - pluginManager.Load(); - pluginManager.DoProcessing(new PPD()); - pluginManager.Unload(); - } - } -} diff --git a/PluginManager/PluginManager.csproj b/PluginManager.csproj similarity index 100% rename from PluginManager/PluginManager.csproj rename to PluginManager.csproj diff --git a/PluginManager/Manager/PluginManager.cs b/PluginManager/Manager/PluginManager.cs deleted file mode 100644 index 6da8f9b..0000000 --- a/PluginManager/Manager/PluginManager.cs +++ /dev/null @@ -1,162 +0,0 @@ -using Crayon; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace PluginManager.Manager -{ - /// - /// Class that handles all plugin handling - /// - /// Type of the Plugin that is going to get loaded - /// Type of PluginDataManager that is going to get used to load data - public class PluginManager where T : Plugin where M : PluginDataManager - { - /// - /// A list of plugins based on their name - /// - public List Plugins { get; private set; } - - private int pluginsSize; - - private PluginDataManager dataManager; - - /// - /// Specifies if you are using the plugin system or not - /// - public bool Enabled { get; set; } - - public PluginManager() - { - Plugins = new List(); - dataManager = (PluginDataManager)Activator.CreateInstance(typeof(M), null); - Enabled = true; // By default it's true - } - - /// - /// Loads all plugins and their data from the plugin folder - /// - public void Load() - { - if (Enabled) - { - if (string.IsNullOrEmpty(PluginManagerConfig.PluginFolderPath)) - throw new ArgumentNullException("Plugins folder path", "You need to set a path for your plugins folder"); - - // First run - if (!Directory.Exists(PluginManagerConfig.PluginFolderPath)) - { - Directory.CreateDirectory(PluginManagerConfig.PluginFolderPath); - return; // Let's return here, no plugins available - } - - // Ignores files in this folder - var pluginDirectories = Directory.GetDirectories(PluginManagerConfig.PluginFolderPath); - - foreach (var pluginPath in pluginDirectories) - { - // Should contain the plugin file - var files = Directory.GetFiles(pluginPath, "*.dll"); - foreach (var dll in files) - { - try - { - // We try to load them if they have at least one class that inherits Plugin - var assembly = Assembly.LoadFrom(dll); - - var types = assembly.GetTypes(); - - foreach (var type in types) - { - if (type.IsSubclassOf(typeof(T))) - addPlugin(type); - } - } - catch (ReflectionTypeLoadException ex) - { - Console.Error.WriteLine(Output.Red($"Failed to load {Path.GetFileName(dll)}. We will continue to load more assemblies"), ex); - } - catch (Exception ex) - { - Console.Error.WriteLine(Output.Red($"Failed to load {Path.GetFileName(dll)}. We will continue to load more assemblies"), ex); - } - } - } - - pluginsSize = Plugins.Count; - } - else - { - // Throw here? - } - } - - /// - /// Saves plugin information to their data files - /// - public void Unload() - { - foreach (var plugin in Plugins) - { - try - { - var dataFilePath = plugin.DataFilePath(); - - // If it's null it means it's not using a file - if (!string.IsNullOrEmpty(dataFilePath)) - dataManager.SaveData(dataFilePath, plugin.Data); - } - catch (Exception) - { - Console.Error.WriteLine(Output.Red($"Couldn't save data for {plugin.Name()}")); - } - } - } - - /// - /// Internal function that handles plugin instancing - /// - /// - internal void addPlugin(Type plugin) - { - Plugin pluginInstance = (T)Activator.CreateInstance(plugin); - - try - { - Plugins.Add((T)pluginInstance); - - var pluginDataFilePath = pluginInstance.DataFilePath(); - - // If it's null we are not using plugin data - if (!string.IsNullOrEmpty(PluginManagerConfig.PluginDataFolderPath) && !string.IsNullOrEmpty(pluginDataFilePath)) - { - var pluginData = dataManager.LoadData(Path.Combine(PluginManagerConfig.PluginDataFolderPath, pluginDataFilePath)); - - // This one shouldn't throw, loadData() will always exist - pluginInstance.loadData(pluginData); - } - - Console.WriteLine($"Loaded {pluginInstance.Name()}"); - } - catch (ArgumentException) - { - Console.Error.WriteLine(Output.Red($"{pluginInstance.Name()} is already loaded")); - } - } - - /// - /// Executes DoProcessing on all plugins - /// - /// - public void DoProcessing(PluginProcessData processData) - { - for (int i = 0; i < pluginsSize; i++) - { - Plugins[i].DoProcessing(processData); - } - } - } -} diff --git a/TestPlugin/Class1.cs b/TestPlugin/Class1.cs deleted file mode 100644 index 7023502..0000000 --- a/TestPlugin/Class1.cs +++ /dev/null @@ -1,50 +0,0 @@ -using PluginManager; -using System; - -namespace TestPlugin -{ - public abstract class Class1 : Plugin - { - public Class1() - { - } - } - - public class asd : Class1 - { - public asd() - { - base.OnLoaded += Asd_OnLoaded; - } - - private void Asd_OnLoaded(PluginData data) - { - Console.Out.WriteLine("I'm loaded man"); - } - - public override void DoProcessing(PluginProcessData data) - { - //Console.WriteLine("working"); - } - - public override string Name() - { - return "asd"; - } - - public override Version Version() - { - return new Version("1.0.0.0"); - } - - public override void BuildDataFile() - { - // Leave this empty if you are not using a data file - } - - public override string DataFilePath() - { - return null; - } - } -} diff --git a/TestPlugin/TestPlugin.csproj b/TestPlugin/TestPlugin.csproj deleted file mode 100644 index 87fe537..0000000 --- a/TestPlugin/TestPlugin.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - netcoreapp3.1 - - - - ..\PluginManager.Test\bin\Debug\Plugins\Yep\ - - - - - - - diff --git a/src/Manager/PluginClassManager.cs b/src/Manager/PluginClassManager.cs new file mode 100644 index 0000000..7d7348f --- /dev/null +++ b/src/Manager/PluginClassManager.cs @@ -0,0 +1,39 @@ +using Crayon; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace PluginManager.Manager +{ + public class PluginClassManager : PluginManagerBase where T : Plugin where M : PluginDataManager + where R : PluginResponseData where P : PluginProcessData + { + /// + /// Loads all plugins and their data from the plugin folder + /// + public override void Load() + { + if (Enabled) + { + // Is this slow? + // I mean.... will this work + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in assemblies) + { + var types = assembly.GetTypes(); + foreach (Type type in types) + { + if (type.IsSubclassOf(typeof(T))) + addPlugin(type); + } + } + + // It's faster if we store them instead of accessing the property + pluginsSize = Plugins.Count; + } + } + } +} diff --git a/PluginManager/Manager/PluginDataManager.cs b/src/Manager/PluginDataManager.cs similarity index 100% rename from PluginManager/Manager/PluginDataManager.cs rename to src/Manager/PluginDataManager.cs diff --git a/src/Manager/PluginDllManager.cs b/src/Manager/PluginDllManager.cs new file mode 100644 index 0000000..b93e4af --- /dev/null +++ b/src/Manager/PluginDllManager.cs @@ -0,0 +1,74 @@ +using Crayon; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace PluginManager.Manager +{ + /// + /// Class that handles all plugin handling + /// + /// Type of the Plugin that is going to get loaded + /// Type of PluginDataManager that is going to get used to load data + /// Type of PluginResponseData that is going to get used to return a response from the plugin list + /// Type of PluginProcess data that is going to get used to process data + public class PluginDllManager : PluginManagerBase where T : Plugin where M : PluginDataManager + where R : PluginResponseData where P : PluginProcessData + { + /// + /// Loads all plugins and their data from the plugin folder + /// + public override void Load() + { + if (Enabled) + { + if (string.IsNullOrEmpty(PluginManagerConfig.PluginFolderPath)) + throw new ArgumentNullException("Plugins folder path", "You need to set a path for your plugins folder"); + + // First run + if (!Directory.Exists(PluginManagerConfig.PluginFolderPath)) + { + Directory.CreateDirectory(PluginManagerConfig.PluginFolderPath); + return; // Let's return here, no plugins available + } + + // Ignores files in this folder + var pluginDirectories = Directory.GetDirectories(PluginManagerConfig.PluginFolderPath); + + foreach (var pluginPath in pluginDirectories) + { + // Should contain the plugin file + var files = Directory.GetFiles(pluginPath, "*.dll"); + + // If it doesn't contain anything, this will not run + foreach (var dll in files) + { + try + { + // We try to load them if they have at least one class that inherits Plugin + var assembly = Assembly.LoadFrom(dll); + + var types = assembly.GetTypes(); + + foreach (var type in types) + { + if (type.IsSubclassOf(typeof(T))) + addPlugin(type); + } + } + catch (ReflectionTypeLoadException ex) + { + Console.Error.WriteLine(Output.Red($"Failed to load {Path.GetFileName(dll)}. We will continue to load more assemblies"), ex); + } + } + } + + // It's faster if we store them instead of accessing the property + pluginsSize = Plugins.Count; + } + } + } +} diff --git a/src/Manager/PluginManagerBase.cs b/src/Manager/PluginManagerBase.cs new file mode 100644 index 0000000..650867d --- /dev/null +++ b/src/Manager/PluginManagerBase.cs @@ -0,0 +1,112 @@ +using Crayon; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace PluginManager.Manager +{ + public abstract class PluginManagerBase where T : Plugin where M : PluginDataManager + where R : PluginResponseData where P : PluginProcessData + { + public PluginManagerBase() + { + DataManager = (M)Activator.CreateInstance(typeof(M), null); + Plugins = new List(); + Enabled = true; // By default it's true + } + + /// + /// Data manager object + /// + internal M DataManager { get; set; } + + /// + /// A list of plugins based on their name + /// + public List Plugins { get; internal set; } + + internal int pluginsSize; + + /// + /// Specifies if you are using the plugin system or not + /// + public bool Enabled { get; set; } + + public abstract void Load(); + + public virtual void Unload() + { + foreach (var plugin in Plugins) + { + try + { + plugin.InvokeOnUnload(); + + if (plugin.UsingDataFile()) + { + var dataFilePath = plugin.DataFileName(); + DataManager.SaveData(dataFilePath, plugin.Data); + } + } + catch (Exception) + { + Console.Error.WriteLine(Output.Red($"Couldn't save data for {plugin.Name()}")); + } + } + } + + /// + /// Executes DoProcessing on all plugins + /// + /// + /// The response object + public virtual R DoProcessing(P processData) + { + R response = (R)Activator.CreateInstance(typeof(R)); + for (int i = 0; i < pluginsSize; i++) + { + Plugins[i].DoProcessing(processData, ref response); + } + + return response; + } + + /// + /// Internal function that handles plugin instancing + /// + /// + internal void addPlugin(Type type) + { + Plugin plugin = (T)Activator.CreateInstance(type); + + var pluginName = plugin.Name(); + + // check versions too? + // and load the most recent one? + for (int i = 0; i < pluginsSize; i++) + { + if (Plugins[i].Name() == pluginName) + { + Console.Error.WriteLine(Output.Red($"{pluginName} is already loaded")); + return; + } + } + + Plugins.Add((T)plugin); + + if (plugin.UsingDataFile()) + { + var pluginDataFilePath = plugin.DataFileName(); + if (string.IsNullOrEmpty(pluginDataFilePath)) + throw new ArgumentNullException("Plugin Data File Path", "To use data files you must specify a name for the file"); + + var pluginData = DataManager.LoadData(Path.Combine(PluginManagerConfig.PluginDataFolderPath, pluginDataFilePath)); + + plugin.loadData(pluginData); + } + + Console.WriteLine($"Loaded {pluginName}"); + } + } +} diff --git a/PluginManager/Plugin/Plugin.cs b/src/Plugin/Plugin.cs similarity index 58% rename from PluginManager/Plugin/Plugin.cs rename to src/Plugin/Plugin.cs index da53ba7..a012a45 100644 --- a/PluginManager/Plugin/Plugin.cs +++ b/src/Plugin/Plugin.cs @@ -4,18 +4,30 @@ using System.Text; namespace PluginManager { + public delegate void OnLoadEvent(); public delegate void OnLoadedEvent(PluginData data); + public delegate void OnUnloadEvent(); /// /// Base class to create plugins /// - public abstract class Plugin + public abstract class Plugin where R : PluginResponseData where P : PluginProcessData { + /// + /// Event executed when plugin object is instanciated + /// + public event OnLoadEvent OnLoad; + /// /// Event executed when the plugin manager finishes loading this plugin /// public event OnLoadedEvent OnLoaded; + /// + /// Event executed when DataManager starts unloading the plugin + /// + public event OnUnloadEvent OnUnload; + /// /// The plugin data object /// @@ -23,7 +35,12 @@ namespace PluginManager public Plugin() { - // Need a constructor to use Activator.CreateInstance() + OnLoad?.Invoke(); + } + + internal void InvokeOnUnload() + { + OnUnload?.Invoke(); } /// @@ -32,8 +49,6 @@ namespace PluginManager /// Data object that is going to get loaded internal void loadData(PluginData data) { - if (data == null) - BuildDataFile(); this.Data = data; OnLoaded?.Invoke(data); } @@ -55,16 +70,24 @@ namespace PluginManager /// The name of the plugin public abstract string Name(); + /// + /// Specifies whether we are using a data file or not + /// + /// Whether we are using a data file or not + public abstract bool UsingDataFile(); + /// /// The path to the plugin data file /// /// The path to the plugin data file - public abstract string DataFilePath(); + public abstract string DataFileName(); /// /// Base function that should get called everytime this plugin needs to process data /// - /// - public abstract void DoProcessing(PluginProcessData data); + /// The data the plugin needs to do processing + /// Response object that the plugin will append to + /// The same object that is passed to the response parameter + public abstract ref R DoProcessing(P data, ref R response); } } diff --git a/PluginManager/Plugin/PluginData.cs b/src/Plugin/PluginData.cs similarity index 100% rename from PluginManager/Plugin/PluginData.cs rename to src/Plugin/PluginData.cs diff --git a/PluginManager/Plugin/PluginProcessData.cs b/src/Plugin/PluginProcessData.cs similarity index 100% rename from PluginManager/Plugin/PluginProcessData.cs rename to src/Plugin/PluginProcessData.cs diff --git a/src/Plugin/PluginResponseData.cs b/src/Plugin/PluginResponseData.cs new file mode 100644 index 0000000..faf25a5 --- /dev/null +++ b/src/Plugin/PluginResponseData.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PluginManager +{ + public abstract class PluginResponseData + { + } +} diff --git a/PluginManager/PluginConfig.cs b/src/PluginConfig.cs similarity index 54% rename from PluginManager/PluginConfig.cs rename to src/PluginConfig.cs index 8120d3e..aa65f26 100644 --- a/PluginManager/PluginConfig.cs +++ b/src/PluginConfig.cs @@ -6,7 +6,14 @@ namespace PluginManager { public static class PluginManagerConfig { + /// + /// Path to the folder where plugin data is going to be stored + /// public static string PluginDataFolderPath { get; set; } + + /// + /// Path to the folder where plugins are going to be stored + /// public static string PluginFolderPath { get; set; } } }