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

@ -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>

View File

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

View File

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

View File

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

View File

@ -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>

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,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}");
}
}
}

View File

@ -4,18 +4,30 @@ using System.Text;
namespace PluginManager namespace PluginManager
{ {
public delegate void OnLoadEvent();
public delegate void OnLoadedEvent(PluginData data); public delegate void OnLoadedEvent(PluginData data);
public delegate void OnUnloadEvent();
/// <summary> /// <summary>
/// Base class to create plugins /// Base class to create plugins
/// </summary> /// </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> /// <summary>
/// Event executed when the plugin manager finishes loading this plugin /// Event executed when the plugin manager finishes loading this plugin
/// </summary> /// </summary>
public event OnLoadedEvent OnLoaded; public event OnLoadedEvent OnLoaded;
/// <summary>
/// Event executed when DataManager starts unloading the plugin
/// </summary>
public event OnUnloadEvent OnUnload;
/// <summary> /// <summary>
/// The plugin data object /// The plugin data object
/// </summary> /// </summary>
@ -23,7 +35,12 @@ namespace PluginManager
public Plugin() public Plugin()
{ {
// Need a constructor to use Activator.CreateInstance() OnLoad?.Invoke();
}
internal void InvokeOnUnload()
{
OnUnload?.Invoke();
} }
/// <summary> /// <summary>
@ -32,8 +49,6 @@ namespace PluginManager
/// <param name="data">Data object that is going to get loaded</param> /// <param name="data">Data object that is going to get loaded</param>
internal void loadData(PluginData data) internal void loadData(PluginData data)
{ {
if (data == null)
BuildDataFile();
this.Data = data; this.Data = data;
OnLoaded?.Invoke(data); OnLoaded?.Invoke(data);
} }
@ -55,16 +70,24 @@ namespace PluginManager
/// <returns>The name of the plugin</returns> /// <returns>The name of the plugin</returns>
public abstract string Name(); 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> /// <summary>
/// The path to the plugin data file /// The path to the plugin data file
/// </summary> /// </summary>
/// <returns>The path to the plugin data file</returns> /// <returns>The path to the plugin data file</returns>
public abstract string DataFilePath(); public abstract string DataFileName();
/// <summary> /// <summary>
/// Base function that should get called everytime this plugin needs to process data /// Base function that should get called everytime this plugin needs to process data
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data">The data the plugin needs to do processing</param>
public abstract void DoProcessing(PluginProcessData data); /// <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);
} }
} }

View File

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

View File

@ -6,7 +6,14 @@ namespace PluginManager
{ {
public static class PluginManagerConfig public static class PluginManagerConfig
{ {
/// <summary>
/// Path to the folder where plugin data is going to be stored
/// </summary>
public static string PluginDataFolderPath { get; set; } 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; } public static string PluginFolderPath { get; set; }
} }
} }