Project restructure

This commit is contained in:
xx-TheDoctor-xx
2020-05-14 17:06:45 +01:00
parent faddda7dbc
commit 6b336964a7
15 changed files with 272 additions and 293 deletions

View 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;
}
}
}
}

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

View 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;
}
}
}
}

View 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
View 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
View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace PluginManager
{
public abstract class PluginData
{
}
}

View 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
{
}
}

View 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
View 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; }
}
}