Project restructure
This commit is contained in:
@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PluginManager\PluginManager.csproj" />
|
||||
<ProjectReference Include="..\TestPlugin\TestPlugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -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<Class1, PDM> pluginManager = new PluginManager<Class1, PDM>();
|
||||
PluginManagerConfig.PluginFolderPath = "Plugins";
|
||||
pluginManager.Load();
|
||||
pluginManager.DoProcessing(new PPD());
|
||||
pluginManager.Unload();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <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>
|
||||
public class PluginManager<T, M> where T : Plugin where M : PluginDataManager
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of plugins based on their name
|
||||
/// </summary>
|
||||
public List<T> Plugins { get; private set; }
|
||||
|
||||
private int pluginsSize;
|
||||
|
||||
private PluginDataManager dataManager;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies if you are using the plugin system or not
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public PluginManager()
|
||||
{
|
||||
Plugins = new List<T>();
|
||||
dataManager = (PluginDataManager)Activator.CreateInstance(typeof(M), null);
|
||||
Enabled = true; // By default it's true
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads all plugins and their data from the plugin folder
|
||||
/// </summary>
|
||||
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?
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves plugin information to their data files
|
||||
/// </summary>
|
||||
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()}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal function that handles plugin instancing
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes DoProcessing on all plugins
|
||||
/// </summary>
|
||||
/// <param name="processData"></param>
|
||||
public void DoProcessing(PluginProcessData processData)
|
||||
{
|
||||
for (int i = 0; i < pluginsSize; i++)
|
||||
{
|
||||
Plugins[i].DoProcessing(processData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<OutputPath>..\PluginManager.Test\bin\Debug\Plugins\Yep\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PluginManager\PluginManager.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,18 +4,30 @@ 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
|
||||
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>
|
||||
@ -23,7 +35,12 @@ namespace PluginManager
|
||||
|
||||
public Plugin()
|
||||
{
|
||||
// Need a constructor to use Activator.CreateInstance()
|
||||
OnLoad?.Invoke();
|
||||
}
|
||||
|
||||
internal void InvokeOnUnload()
|
||||
{
|
||||
OnUnload?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -32,8 +49,6 @@ namespace PluginManager
|
||||
/// <param name="data">Data object that is going to get loaded</param>
|
||||
internal void loadData(PluginData data)
|
||||
{
|
||||
if (data == null)
|
||||
BuildDataFile();
|
||||
this.Data = data;
|
||||
OnLoaded?.Invoke(data);
|
||||
}
|
||||
@ -55,16 +70,24 @@ namespace PluginManager
|
||||
/// <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 DataFilePath();
|
||||
public abstract string DataFileName();
|
||||
|
||||
/// <summary>
|
||||
/// Base function that should get called everytime this plugin needs to process data
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public abstract void DoProcessing(PluginProcessData data);
|
||||
/// <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/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
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,14 @@ 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