Project restructure
This commit is contained in:
39
src/Manager/PluginClassManager.cs
Normal file
39
src/Manager/PluginClassManager.cs
Normal file
@ -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<T, M, R, P> : PluginManagerBase<T, M, R, P> where T : Plugin<R, P> where M : PluginDataManager
|
||||
where R : PluginResponseData where P : PluginProcessData
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads all plugins and their data from the plugin folder
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/Manager/PluginDataManager.cs
Normal file
31
src/Manager/PluginDataManager.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PluginManager
|
||||
{
|
||||
/// <summary>
|
||||
/// This class implements all the plugin data information handling
|
||||
/// </summary>
|
||||
public abstract class PluginDataManager
|
||||
{
|
||||
public PluginDataManager()
|
||||
{
|
||||
// Need a constructor to use Activator.CreateInstance()
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the data for a specific plugin
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The path to the plugin data file</param>
|
||||
/// <returns>Plugin data object if found, else return null, the plugin must handle this</returns>
|
||||
public abstract PluginData LoadData(string pluginDataFileName);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the data to the plugin data file
|
||||
/// </summary>
|
||||
/// <param name="pluginName">The path to the plugin data file</param>
|
||||
/// <param name="data">Data object to save</param>
|
||||
public abstract void SaveData(string pluginDataFileName, PluginData data);
|
||||
}
|
||||
}
|
||||
74
src/Manager/PluginDllManager.cs
Normal file
74
src/Manager/PluginDllManager.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that handles all plugin handling
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the Plugin that is going to get loaded</typeparam>
|
||||
/// <typeparam name="M">Type of PluginDataManager that is going to get used to load data</typeparam>
|
||||
/// <typeparam name="R">Type of PluginResponseData that is going to get used to return a response from the plugin list</typeparam>
|
||||
/// <typeparam name="P">Type of PluginProcess data that is going to get used to process data</typeparam>
|
||||
public class PluginDllManager<T, M, R, P> : PluginManagerBase<T, M, R, P> where T : Plugin<R, P> where M : PluginDataManager
|
||||
where R : PluginResponseData where P : PluginProcessData
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads all plugins and their data from the plugin folder
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
112
src/Manager/PluginManagerBase.cs
Normal file
112
src/Manager/PluginManagerBase.cs
Normal file
@ -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<T, M, R, P> where T : Plugin<R, P> where M : PluginDataManager
|
||||
where R : PluginResponseData where P : PluginProcessData
|
||||
{
|
||||
public PluginManagerBase()
|
||||
{
|
||||
DataManager = (M)Activator.CreateInstance(typeof(M), null);
|
||||
Plugins = new List<T>();
|
||||
Enabled = true; // By default it's true
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data manager object
|
||||
/// </summary>
|
||||
internal M DataManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of plugins based on their name
|
||||
/// </summary>
|
||||
public List<T> Plugins { get; internal set; }
|
||||
|
||||
internal int pluginsSize;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies if you are using the plugin system or not
|
||||
/// </summary>
|
||||
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()}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes DoProcessing on all plugins
|
||||
/// </summary>
|
||||
/// <param name="processData"></param>
|
||||
/// <returns>The response object</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal function that handles plugin instancing
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
internal void addPlugin(Type type)
|
||||
{
|
||||
Plugin<R, P> 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
93
src/Plugin/Plugin.cs
Normal file
93
src/Plugin/Plugin.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PluginManager
|
||||
{
|
||||
public delegate void OnLoadEvent();
|
||||
public delegate void OnLoadedEvent(PluginData data);
|
||||
public delegate void OnUnloadEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Base class to create plugins
|
||||
/// </summary>
|
||||
public abstract class Plugin<R, P> where R : PluginResponseData where P : PluginProcessData
|
||||
{
|
||||
/// <summary>
|
||||
/// Event executed when plugin object is instanciated
|
||||
/// </summary>
|
||||
public event OnLoadEvent OnLoad;
|
||||
|
||||
/// <summary>
|
||||
/// Event executed when the plugin manager finishes loading this plugin
|
||||
/// </summary>
|
||||
public event OnLoadedEvent OnLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Event executed when DataManager starts unloading the plugin
|
||||
/// </summary>
|
||||
public event OnUnloadEvent OnUnload;
|
||||
|
||||
/// <summary>
|
||||
/// The plugin data object
|
||||
/// </summary>
|
||||
public PluginData Data { get; private set; }
|
||||
|
||||
public Plugin()
|
||||
{
|
||||
OnLoad?.Invoke();
|
||||
}
|
||||
|
||||
internal void InvokeOnUnload()
|
||||
{
|
||||
OnUnload?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal function that handles data loading
|
||||
/// </summary>
|
||||
/// <param name="data">Data object that is going to get loaded</param>
|
||||
internal void loadData(PluginData data)
|
||||
{
|
||||
this.Data = data;
|
||||
OnLoaded?.Invoke(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the default plugin file for when the plugin is first instanciated
|
||||
/// </summary>
|
||||
public abstract void BuildDataFile();
|
||||
|
||||
/// <summary>
|
||||
/// The version of the plugin
|
||||
/// </summary>
|
||||
/// <returns>The version of the plugin</returns>
|
||||
public abstract Version Version();
|
||||
|
||||
/// <summary>
|
||||
/// The version of the plugin
|
||||
/// </summary>
|
||||
/// <returns>The name of the plugin</returns>
|
||||
public abstract string Name();
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether we are using a data file or not
|
||||
/// </summary>
|
||||
/// <returns>Whether we are using a data file or not</returns>
|
||||
public abstract bool UsingDataFile();
|
||||
|
||||
/// <summary>
|
||||
/// The path to the plugin data file
|
||||
/// </summary>
|
||||
/// <returns>The path to the plugin data file</returns>
|
||||
public abstract string DataFileName();
|
||||
|
||||
/// <summary>
|
||||
/// Base function that should get called everytime this plugin needs to process data
|
||||
/// </summary>
|
||||
/// <param name="data">The data the plugin needs to do processing</param>
|
||||
/// <param name="response">Response object that the plugin will append to</param>
|
||||
/// <returns>The same object that is passed to the response parameter</returns>
|
||||
public abstract ref R DoProcessing(P data, ref R response);
|
||||
}
|
||||
}
|
||||
10
src/Plugin/PluginData.cs
Normal file
10
src/Plugin/PluginData.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PluginManager
|
||||
{
|
||||
public abstract class PluginData
|
||||
{
|
||||
}
|
||||
}
|
||||
13
src/Plugin/PluginProcessData.cs
Normal file
13
src/Plugin/PluginProcessData.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PluginManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for plugin process data that gets passes to Plugin.DoProcessing()
|
||||
/// </summary>
|
||||
public abstract class PluginProcessData
|
||||
{
|
||||
}
|
||||
}
|
||||
10
src/Plugin/PluginResponseData.cs
Normal file
10
src/Plugin/PluginResponseData.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PluginManager
|
||||
{
|
||||
public abstract class PluginResponseData
|
||||
{
|
||||
}
|
||||
}
|
||||
19
src/PluginConfig.cs
Normal file
19
src/PluginConfig.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace PluginManager
|
||||
{
|
||||
public static class PluginManagerConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to the folder where plugin data is going to be stored
|
||||
/// </summary>
|
||||
public static string PluginDataFolderPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the folder where plugins are going to be stored
|
||||
/// </summary>
|
||||
public static string PluginFolderPath { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user