New PluginLoader class fully tested and functional

This commit is contained in:
Neil Dorin
2020-03-25 21:25:56 -06:00
parent 733dbf9bd7
commit 1418e8f8c6
4 changed files with 304 additions and 140 deletions

View File

@@ -26,6 +26,20 @@ namespace PepperDash.Essentials
/// </summary> /// </summary>
static List<LoadedAssembly> LoadedPluginFolderAssemblies; static List<LoadedAssembly> LoadedPluginFolderAssemblies;
/// <summary>
/// The directory to look in for .cplz plugin packages
/// </summary>
static string _pluginDirectory = Global.FilePathPrefix + "plugins";
/// <summary>
/// The directory where plugins will be moved to and loaded from
/// </summary>
static string _loadedPluginsDirectoryPath = _pluginDirectory + Global.DirectorySeparator + "loadedAssemblies";
// The temp directory where .cplz archives will be unzipped to
static string _tempDirectory = _pluginDirectory + Global.DirectorySeparator + "temp";
static PluginLoader() static PluginLoader()
{ {
LoadedAssemblies = new List<LoadedAssembly>(); LoadedAssemblies = new List<LoadedAssembly>();
@@ -37,19 +51,43 @@ namespace PepperDash.Essentials
/// </summary> /// </summary>
public static void AddProgramAssemblies() public static void AddProgramAssemblies()
{ {
Debug.Console(2, "Getting Assemblies loaded with Essentials");
// Get the loaded assembly filenames // Get the loaded assembly filenames
var appDi = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix); var appDi = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix);
var assemblyFiles = appDi.GetFiles("*.dll"); var assemblyFiles = appDi.GetFiles("*.dll");
foreach (var file in assemblyFiles) Debug.Console(2, "Found {0} Assemblies", assemblyFiles.Length);
foreach (var fi in assemblyFiles)
{ {
try string version = string.Empty;
Assembly assembly = null;
switch (fi.Name)
{ {
LoadAssembly(file.FullName); case ("PepperDashEssentials.dll"):
{
version = Global.AssemblyVersion;
assembly = Assembly.GetExecutingAssembly();
break;
} }
catch case ("PepperDash_Core.dll"):
{ {
Debug.Console(2, "Assembly {0} is not a custom assembly", file.FullName); version = PepperDash.Core.Debug.PepperDashCoreVersion;
break;
}
}
LoadedAssemblies.Add(new LoadedAssembly(fi.Name, version, assembly));
}
if (Debug.Level > 1)
{
Debug.Console(2, "Loaded Assemblies:");
foreach (var assembly in LoadedAssemblies)
{
Debug.Console(2, "Assembly: {0}", assembly.Name);
} }
} }
} }
@@ -58,29 +96,22 @@ namespace PepperDash.Essentials
/// Loads an assembly via Reflection and adds it to the list of loaded assemblies /// Loads an assembly via Reflection and adds it to the list of loaded assemblies
/// </summary> /// </summary>
/// <param name="fileName"></param> /// <param name="fileName"></param>
static LoadedAssembly LoadAssembly(string fileName) static LoadedAssembly LoadAssembly(string filePath)
{ {
if (!CheckIfAssemblyExists(fileName)) Debug.Console(2, "Attempting to load {0}", filePath);
{ var assembly = Assembly.LoadFrom(filePath);
var assembly = Assembly.LoadFrom(fileName);
if (assembly != null) if (assembly != null)
{ {
var assyVersion = GetAssemblyVersion(assembly); var assyVersion = GetAssemblyVersion(assembly);
var loadedAssembly = new LoadedAssembly(assembly.GetName().Name, assyVersion, assembly); var loadedAssembly = new LoadedAssembly(assembly.GetName().Name, assyVersion, assembly);
LoadedAssemblies.Add(loadedAssembly); LoadedAssemblies.Add(loadedAssembly);
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loaded plugin file '{0}', version {1}", loadedAssembly.FileName, loadedAssembly.Version); Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loaded assembly '{0}', version {1}", loadedAssembly.Name, loadedAssembly.Version);
return loadedAssembly; return loadedAssembly;
} }
else else
{ {
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Unable to load assembly: '{0}'", fileName); Debug.Console(0, Debug.ErrorLogLevel.Notice, "Unable to load assembly: '{0}'", filePath);
}
}
else
{
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Skipping assembly: {0}. There is already an assembly with that name loaded.", fileName);
} }
return null; return null;
@@ -95,7 +126,7 @@ namespace PepperDash.Essentials
static string GetAssemblyVersion(Assembly assembly) static string GetAssemblyVersion(Assembly assembly)
{ {
var ver = assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false); var ver = assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
if (ver != null) if (ver != null && ver.Length > 0)
{ {
// Get the AssemblyInformationalVersion // Get the AssemblyInformationalVersion
AssemblyInformationalVersionAttribute verAttribute = ver[0] as AssemblyInformationalVersionAttribute; AssemblyInformationalVersionAttribute verAttribute = ver[0] as AssemblyInformationalVersionAttribute;
@@ -115,17 +146,19 @@ namespace PepperDash.Essentials
/// </summary> /// </summary>
/// <param name="filename"></param> /// <param name="filename"></param>
/// <returns>True if file already matches loaded assembly file.</returns> /// <returns>True if file already matches loaded assembly file.</returns>
public static bool CheckIfAssemblyExists(string filename) public static bool CheckIfAssemblyLoaded(string name)
{ {
Debug.Console(2, "Checking if assembly: {0} is loaded...", filename); Debug.Console(2, "Checking if assembly: {0} is loaded...", name);
var loadedAssembly = LoadedAssemblies.FirstOrDefault(s => s.FileName.Equals(filename)); var loadedAssembly = LoadedAssemblies.FirstOrDefault(s => s.Name.Equals(name));
if (loadedAssembly != null) if (loadedAssembly != null)
{ {
Debug.Console(2, "Assembly already loaded.");
return true; return true;
} }
else else
{ {
Debug.Console(2, "Assembly not loaded.");
return false; return false;
} }
} }
@@ -139,60 +172,161 @@ namespace PepperDash.Essentials
Debug.Console(0, "Loaded Assemblies:"); Debug.Console(0, "Loaded Assemblies:");
foreach (var assembly in LoadedAssemblies) foreach (var assembly in LoadedAssemblies)
{ {
Debug.Console(0, "{0} Version: {1}", assembly.FileName, assembly.Version); Debug.Console(0, "{0} Version: {1}", assembly.Name, assembly.Version);
} }
} }
/// <summary> /// <summary>
/// Loads plugins /// Moves any .dll assemblies not already loaded from the plugins folder to loadedPlugins folder
/// </summary> /// </summary>
public static void LoadPlugins() static void MoveDllAssemblies()
{ {
var dir = Global.FilePathPrefix + "plugins"; Debug.Console(0, "Looking for .dll assemblies from plugins folder...");
var tempDir = dir + Global.DirectorySeparator + "temp";
if (Directory.Exists(dir)) var pluginDi = new DirectoryInfo(_pluginDirectory);
var pluginFiles = pluginDi.GetFiles("*.dll");
if (pluginFiles.Length > 0)
{ {
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Plugins directory found, checking for factory plugins"); if (!Directory.Exists(_loadedPluginsDirectoryPath))
var di = new DirectoryInfo(dir);
var zFiles = di.GetFiles("*.cplz");
foreach (var fi in zFiles)
{ {
Directory.CreateDirectory(tempDir); Directory.CreateDirectory(_loadedPluginsDirectoryPath);
var tempDi = new DirectoryInfo(tempDir); }
}
Debug.Console(0, "Found cplz: {0}. Unzipping into temp plugins directory", fi.Name); foreach (var pluginFile in pluginFiles)
var result = CrestronZIP.Unzip(fi.FullName, tempDi.FullName);
Debug.Console(0, "UnZip Result: {0}", result.ToString());
var files = tempDi.GetFiles("*.dll");
foreach (FileInfo file in files)
{ {
try try
{ {
var pluginAssembly = LoadAssembly(fi.FullName); Debug.Console(0, "Found .dll: {0}", pluginFile.Name);
if (pluginAssembly != null) if (!CheckIfAssemblyLoaded(pluginFile.Name))
{ {
LoadedPluginFolderAssemblies.Add(pluginAssembly); string filePath = string.Empty;
filePath = _loadedPluginsDirectoryPath + Global.DirectorySeparator + pluginFile.Name;
// Check if there is a previous file in the loadedPlugins directory and delete
if (File.Exists(filePath))
{
Debug.Console(0, "Found existing file in loadedPlugins: {0} Deleting and moving new file to replace it", filePath);
File.Delete(filePath);
}
// Move the file
File.Move(pluginFile.FullName, filePath);
Debug.Console(2, "Moved {0} to {1}", pluginFile.FullName, filePath);
}
else
{
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Skipping assembly: {0}. There is already an assembly with that name loaded.", pluginFile.FullName);
} }
} }
catch catch (Exception e)
{ {
Debug.Console(2, "Assembly {0} is not a custom assembly", file.FullName); Debug.Console(2, "Error with plugin file {0} . Exception: {1}", pluginFile.FullName, e);
continue; //catching any load issues and continuing. There will be exceptions loading Crestron .dlls from the cplz Probably should do something different here continue; //catching any load issues and continuing. There will be exceptions loading Crestron .dlls from the cplz Probably should do something different here
} }
}
Debug.Console(0, "Done with .dll assemblies");
}
/// <summary>
/// Unzips each .cplz archive into the temp directory and moves any unloaded files into loadedPlugins
/// </summary>
static void UnzipAndMoveCplzArchives()
{
Debug.Console(0, "Looking for .cplz archives from plugins folder...");
var di = new DirectoryInfo(_pluginDirectory);
var zFiles = di.GetFiles("*.cplz");
if (zFiles.Length > 0)
{
if (!Directory.Exists(_loadedPluginsDirectoryPath))
{
Directory.CreateDirectory(_loadedPluginsDirectoryPath);
}
}
foreach (var zfi in zFiles)
{
Directory.CreateDirectory(_tempDirectory);
var tempDi = new DirectoryInfo(_tempDirectory);
Debug.Console(0, "Found cplz: {0}. Unzipping into temp plugins directory", zfi.Name);
var result = CrestronZIP.Unzip(zfi.FullName, tempDi.FullName);
Debug.Console(0, "UnZip Result: {0}", result.ToString());
var tempFiles = tempDi.GetFiles("*.dll");
foreach (var tempFile in tempFiles)
{
try
{
if (!CheckIfAssemblyLoaded(tempFile.Name))
{
string filePath = string.Empty;
filePath = _loadedPluginsDirectoryPath + Global.DirectorySeparator + tempFile.Name;
// Check if there is a previous file in the loadedPlugins directory and delete
if (File.Exists(filePath))
{
Debug.Console(0, "Found existing file in loadedPlugins: {0} Deleting and moving new file to replace it", filePath);
File.Delete(filePath);
}
// Move the file
File.Move(tempFile.FullName, filePath);
Debug.Console(2, "Moved {0} to {1}", tempFile.FullName, filePath);
}
else
{
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Skipping assembly: {0}. There is already an assembly with that name loaded.", tempFile.FullName);
}
}
catch (Exception e)
{
Debug.Console(2, "Assembly {0} is not a custom assembly. Exception: {1}", tempFile.FullName, e);
continue; //catching any load issues and continuing. There will be exceptions loading Crestron .dlls from the cplz Probably should do something different here
}
} }
// Delete the .cplz and the temp directory // Delete the .cplz and the temp directory
fi.Delete(); Directory.Delete(_tempDirectory, true);
Directory.Delete(tempDir); zfi.Delete();
} }
// Iterate the loaded assemblies and try to call the LoadPlugin method Debug.Console(0, "Done with .cplz archives");
}
/// <summary>
/// Attempts to load the assemblies from the loadedPlugins folder
/// </summary>
static void LoadPluginAssemblies()
{
Debug.Console(0, "Loading assemblies from loadedPlugins folder...");
var pluginDi = new DirectoryInfo(_loadedPluginsDirectoryPath);
var pluginFiles = pluginDi.GetFiles("*.dll");
Debug.Console(2, "Found {0} plugin assemblies to load", pluginFiles.Length);
foreach (var pluginFile in pluginFiles)
{
var loadedAssembly = LoadAssembly(pluginFile.FullName);
LoadedPluginFolderAssemblies.Add(loadedAssembly);
}
Debug.Console(0, "All Plugins Loaded.");
}
/// <summary>
/// Iterate the loaded assemblies and try to call the LoadPlugin method
/// </summary>
static void LoadCustomPluginTypes()
{
Debug.Console(0, "Loading Custom Plugin Types...");
foreach (var loadedAssembly in LoadedPluginFolderAssemblies) foreach (var loadedAssembly in LoadedPluginFolderAssemblies)
{ {
// iteratate this assembly's classes, looking for "LoadPlugin()" methods // iteratate this assembly's classes, looking for "LoadPlugin()" methods
@@ -243,26 +377,49 @@ namespace PepperDash.Essentials
Debug.Console(0, Debug.ErrorLogLevel.Warning, "MinimumEssentialsFrameworkVersion not found. Loading plugin, but your mileage may vary."); Debug.Console(0, Debug.ErrorLogLevel.Warning, "MinimumEssentialsFrameworkVersion not found. Loading plugin, but your mileage may vary.");
} }
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Adding plugin: {0}", loadedAssembly.FileName); Debug.Console(0, Debug.ErrorLogLevel.Notice, "Adding plugin: {0}", loadedAssembly.Name);
loadPlugin.Invoke(null, null); loadPlugin.Invoke(null, null);
} }
} }
catch catch (Exception e)
{ {
Debug.Console(2, "Load Plugin not found. {0} is not a plugin assembly", loadedAssembly.FileName); Debug.Console(2, "Load Plugin not found. {0} is not a plugin assembly. Exception: {1}", loadedAssembly.Name, e);
continue; continue;
} }
} }
} }
catch catch (Exception e)
{ {
Debug.Console(2, "Assembly {0} is not a custom assembly. Types cannot be loaded.", loadedAssembly.FileName); Debug.Console(2, "Error Loading Assembly: {0} Exception: (1) ", loadedAssembly.Name, e);
continue; continue;
} }
} }
// plugin dll will be loaded. Any classes in plugin should have a static constructor // plugin dll will be loaded. Any classes in plugin should have a static constructor
// that registers that class with the Core.DeviceFactory // that registers that class with the Core.DeviceFactory
Debug.Console(0, "Done Loading Custom Plugin Types.");
}
/// <summary>
/// Loads plugins
/// </summary>
public static void LoadPlugins()
{
if (Directory.Exists(_pluginDirectory))
{
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Plugins directory found, checking for plugins");
// Deal with any .dll files
MoveDllAssemblies();
// Deal with any .cplz files
UnzipAndMoveCplzArchives();
// Load the assemblies from the loadedPlugins folder into the AppDomain
LoadPluginAssemblies();
// Load the types from any custom plugin assemblies
LoadCustomPluginTypes();
} }
} }
@@ -273,13 +430,13 @@ namespace PepperDash.Essentials
/// </summary> /// </summary>
public class LoadedAssembly public class LoadedAssembly
{ {
public string FileName { get; private set; } public string Name { get; private set; }
public string Version { get; private set; } public string Version { get; private set; }
public Assembly Assembly { get; private set; } public Assembly Assembly { get; private set; }
public LoadedAssembly(string fileName, string version, Assembly assembly) public LoadedAssembly(string name, string version, Assembly assembly)
{ {
FileName = fileName; Name = name;
Version = version; Version = version;
Assembly = assembly; Assembly = assembly;
} }

View File

@@ -64,16 +64,23 @@ namespace PepperDash.Essentials.Core.Config
if (configFiles != null) if (configFiles != null)
{ {
Debug.Console(2, "{0} config files found matching pattern", configFiles.Length);
if (configFiles.Length > 1) if (configFiles.Length > 1)
{ {
Debug.Console(0, Debug.ErrorLogLevel.Error, Debug.Console(0, Debug.ErrorLogLevel.Error,
"****Error: Multiple Portal Configuration files present. Please ensure only a single file exists and reset program.****"); "****Error: Multiple Portal Configuration files present. Please ensure only a single file exists and reset program.****");
return false; return false;
} }
else else if (configFiles.Length == 1)
{ {
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Found Portal config file: '{0}'", filePath); Debug.Console(0, Debug.ErrorLogLevel.Notice, "Found Portal config file: '{0}'", filePath);
} }
else
{
Debug.Console(0, Debug.ErrorLogLevel.Notice, "No config file found.");
return false;
}
} }
else else
{ {

View File

@@ -98,7 +98,7 @@ namespace PepperDash.Essentials.Core
{ {
Debug.Console(2, "Comparing running version '{0}' to minimum version '{1}'", AssemblyVersion, minimumVersion); Debug.Console(2, "Comparing running version '{0}' to minimum version '{1}'", AssemblyVersion, minimumVersion);
var runtimeVersion = Regex.Match(AssemblyVersion, @"^(\d*).(\d*).(\d*)$"); var runtimeVersion = Regex.Match(AssemblyVersion, @"^(\d*).(\d*).(\d*).*");
var runtimeVersionMajor = Int16.Parse(runtimeVersion.Groups[1].Value); var runtimeVersionMajor = Int16.Parse(runtimeVersion.Groups[1].Value);
var runtimeVersionMinor = Int16.Parse(runtimeVersion.Groups[2].Value); var runtimeVersionMinor = Int16.Parse(runtimeVersion.Groups[2].Value);
@@ -107,7 +107,7 @@ namespace PepperDash.Essentials.Core
// Check for beta build version // Check for beta build version
if (runtimeVersionMajor == 0) if (runtimeVersionMajor == 0)
{ {
Debug.Console(2, "Running Beta Build. Bypassing Dependency Check."); Debug.Console(2, "Running Local Build. Bypassing Dependency Check.");
return true; return true;
} }