diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Comm and IR/GenericComm.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Comm and IR/GenericComm.cs
index 86faab64..0b0e4d82 100644
--- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Comm and IR/GenericComm.cs
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Comm and IR/GenericComm.cs
@@ -62,7 +62,7 @@ namespace PepperDash.Essentials.Core
ConfigWriter.UpdateDeviceConfig(config);
}
- public class Factory : EssentialsDevice.Factory
+ public class Factory : Essentials.Core.Factory
{
#region IDeviceFactory Members
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Factory/IDeviceFactory.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Factory/IDeviceFactory.cs
new file mode 100644
index 00000000..0781d647
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Factory/IDeviceFactory.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Crestron.SimplSharp;
+
+namespace PepperDash.Essentials.Core
+{
+ ///
+ /// Defines a class that is capable of loading device types
+ ///
+ public interface IDeviceFactory
+ {
+ ///
+ /// Will be called when the plugin is loaded by Essentials. Must add any new types to the DeviceFactory using DeviceFactory.AddFactoryForType() for each new type
+ ///
+ void LoadTypeFactories();
+ }
+}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj
index 8125a667..c3ea8202 100644
--- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj
@@ -138,6 +138,7 @@
+
@@ -157,8 +158,9 @@
+
-
+
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/IPluginDeviceConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/IPluginDeviceFactory.cs
similarity index 50%
rename from essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/IPluginDeviceConfig.cs
rename to essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/IPluginDeviceFactory.cs
index b8fa03a3..ff979599 100644
--- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/IPluginDeviceConfig.cs
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/IPluginDeviceFactory.cs
@@ -1,5 +1,5 @@
using PepperDash.Core;
-using PepperDash.Essentials.Core.Config;
+
namespace PepperDash.Essentials.Core
{
@@ -15,14 +15,5 @@ namespace PepperDash.Essentials.Core
}
- ///
- /// Defines a class that is capable of loading device types
- ///
- public interface IDeviceFactory
- {
- ///
- /// Will be called when the plugin is loaded by Essentials. Must add any new types to the DeviceFactory using DeviceFactory.AddFactoryForType() for each new type
- ///
- void LoadTypeFactories();
- }
+
}
\ No newline at end of file
diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/PluginLoader.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/PluginLoader.cs
new file mode 100644
index 00000000..3870159d
--- /dev/null
+++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Plugins/PluginLoader.cs
@@ -0,0 +1,445 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Crestron.SimplSharp;
+using Crestron.SimplSharp.CrestronIO;
+using Crestron.SimplSharp.Reflection;
+
+using PepperDash.Core;
+using PepperDash.Essentials.Core;
+
+namespace PepperDash.Essentials
+{
+ ///
+ /// Deals with loading plugins at runtime
+ ///
+ public static class PluginLoader
+ {
+ ///
+ /// The complete list of loaded assemblies. Includes Essentials Framework assemblies and plugins
+ ///
+ public static List LoadedAssemblies { get; private set; }
+
+ ///
+ /// The list of assemblies loaded from the plugins folder
+ ///
+ static List LoadedPluginFolderAssemblies;
+
+ ///
+ /// The directory to look in for .cplz plugin packages
+ ///
+ static string _pluginDirectory = Global.FilePathPrefix + "plugins";
+
+ ///
+ /// The directory where plugins will be moved to and loaded from
+ ///
+ 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()
+ {
+ LoadedAssemblies = new List();
+ LoadedPluginFolderAssemblies = new List();
+ }
+
+ ///
+ /// Retrieves all the loaded assemblies from the program directory
+ ///
+ public static void AddProgramAssemblies()
+ {
+ Debug.Console(2, "Getting Assemblies loaded with Essentials");
+ // Get the loaded assembly filenames
+ var appDi = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix);
+ var assemblyFiles = appDi.GetFiles("*.dll");
+
+ Debug.Console(2, "Found {0} Assemblies", assemblyFiles.Length);
+
+ foreach (var fi in assemblyFiles)
+ {
+ string version = string.Empty;
+ Assembly assembly = null;
+
+ switch (fi.Name)
+ {
+ case ("PepperDashEssentials.dll"):
+ {
+ version = Global.AssemblyVersion;
+ assembly = Assembly.GetExecutingAssembly();
+ break;
+ }
+ case ("PepperDash_Core.dll"):
+ {
+ 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);
+ }
+ }
+ }
+
+ ///
+ /// Loads an assembly via Reflection and adds it to the list of loaded assemblies
+ ///
+ ///
+ static LoadedAssembly LoadAssembly(string filePath)
+ {
+ Debug.Console(2, "Attempting to load {0}", filePath);
+ var assembly = Assembly.LoadFrom(filePath);
+ if (assembly != null)
+ {
+ var assyVersion = GetAssemblyVersion(assembly);
+
+ var loadedAssembly = new LoadedAssembly(assembly.GetName().Name, assyVersion, assembly);
+ LoadedAssemblies.Add(loadedAssembly);
+ Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loaded assembly '{0}', version {1}", loadedAssembly.Name, loadedAssembly.Version);
+ return loadedAssembly;
+ }
+ else
+ {
+ Debug.Console(0, Debug.ErrorLogLevel.Notice, "Unable to load assembly: '{0}'", filePath);
+ }
+
+ return null;
+
+ }
+
+ ///
+ /// Attempts to get the assembly informational version and if not possible gets the version
+ ///
+ ///
+ ///
+ static string GetAssemblyVersion(Assembly assembly)
+ {
+ var ver = assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
+ if (ver != null && ver.Length > 0)
+ {
+ // Get the AssemblyInformationalVersion
+ AssemblyInformationalVersionAttribute verAttribute = ver[0] as AssemblyInformationalVersionAttribute;
+ return verAttribute.InformationalVersion;
+ }
+ else
+ {
+ // Get the AssemblyVersion
+ var version = assembly.GetName().Version;
+ var verStr = string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision);
+ return verStr;
+ }
+ }
+
+ ///
+ /// Checks if the filename matches an already loaded assembly file's name
+ ///
+ ///
+ /// True if file already matches loaded assembly file.
+ public static bool CheckIfAssemblyLoaded(string name)
+ {
+ Debug.Console(2, "Checking if assembly: {0} is loaded...", name);
+ var loadedAssembly = LoadedAssemblies.FirstOrDefault(s => s.Name.Equals(name));
+
+ if (loadedAssembly != null)
+ {
+ Debug.Console(2, "Assembly already loaded.");
+ return true;
+ }
+ else
+ {
+ Debug.Console(2, "Assembly not loaded.");
+ return false;
+ }
+ }
+
+ ///
+ /// Used by console command to report the currently loaded assemblies and versions
+ ///
+ ///
+ public static void ReportAssemblyVersions(string command)
+ {
+ Debug.Console(0, "Loaded Assemblies:");
+ foreach (var assembly in LoadedAssemblies)
+ {
+ Debug.Console(0, "{0} Version: {1}", assembly.Name, assembly.Version);
+ }
+ }
+
+ ///
+ /// Moves any .dll assemblies not already loaded from the plugins folder to loadedPlugins folder
+ ///
+ static void MoveDllAssemblies()
+ {
+ Debug.Console(0, "Looking for .dll assemblies from plugins folder...");
+
+ var pluginDi = new DirectoryInfo(_pluginDirectory);
+ var pluginFiles = pluginDi.GetFiles("*.dll");
+
+ if (pluginFiles.Length > 0)
+ {
+ if (!Directory.Exists(_loadedPluginsDirectoryPath))
+ {
+ Directory.CreateDirectory(_loadedPluginsDirectoryPath);
+ }
+ }
+
+ foreach (var pluginFile in pluginFiles)
+ {
+ try
+ {
+ Debug.Console(0, "Found .dll: {0}", pluginFile.Name);
+
+ if (!CheckIfAssemblyLoaded(pluginFile.Name))
+ {
+ 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 (Exception e)
+ {
+ 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
+ }
+ }
+
+ Debug.Console(0, "Done with .dll assemblies");
+ }
+
+ ///
+ /// Unzips each .cplz archive into the temp directory and moves any unloaded files into loadedPlugins
+ ///
+ 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
+ Directory.Delete(_tempDirectory, true);
+ zfi.Delete();
+ }
+
+ Debug.Console(0, "Done with .cplz archives");
+ }
+
+ ///
+ /// Attempts to load the assemblies from the loadedPlugins folder
+ ///
+ 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.");
+ }
+
+ ///
+ /// Iterate the loaded assemblies and try to call the LoadPlugin method
+ ///
+ static void LoadCustomPluginTypes()
+ {
+ Debug.Console(0, "Loading Custom Plugin Types...");
+ foreach (var loadedAssembly in LoadedPluginFolderAssemblies)
+ {
+ // iteratate this assembly's classes, looking for "LoadPlugin()" methods
+ try
+ {
+ var assy = loadedAssembly.Assembly;
+ var types = assy.GetTypes();
+ foreach (var type in types)
+ {
+ try
+ {
+ var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Static);
+ var loadPlugin = methods.FirstOrDefault(m => m.Name.Equals("LoadPlugin"));
+ if (loadPlugin != null)
+ {
+ Debug.Console(2, "LoadPlugin method found in {0}", type.Name);
+
+ var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static);
+
+ var minimumVersion = fields.FirstOrDefault(p => p.Name.Equals("MinimumEssentialsFrameworkVersion"));
+ if (minimumVersion != null)
+ {
+ Debug.Console(2, "MinimumEssentialsFrameworkVersion found");
+
+ var minimumVersionString = minimumVersion.GetValue(null) as string;
+
+ if (!string.IsNullOrEmpty(minimumVersionString))
+ {
+ var passed = Global.IsRunningMinimumVersionOrHigher(minimumVersionString);
+
+ if (!passed)
+ {
+ Debug.Console(0, Debug.ErrorLogLevel.Error, "Plugin indicates minimum Essentials version {0}. Dependency check failed. Skipping Plugin", minimumVersionString);
+ continue;
+ }
+ else
+ {
+ Debug.Console(0, Debug.ErrorLogLevel.Notice, "Passed plugin passed dependency check (required version {0})", minimumVersionString);
+ }
+ }
+ else
+ {
+ Debug.Console(0, Debug.ErrorLogLevel.Warning, "MinimumEssentialsFrameworkVersion found but not set. Loading plugin, but your mileage may vary.");
+ }
+ }
+ else
+ {
+ 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.Name);
+ loadPlugin.Invoke(null, null);
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.Console(2, "Load Plugin not found. {0} is not a plugin assembly. Exception: {1}", loadedAssembly.Name, e);
+ continue;
+ }
+
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.Console(2, "Error Loading Assembly: {0} Exception: (1) ", loadedAssembly.Name, e);
+ continue;
+ }
+ }
+ // plugin dll will be loaded. Any classes in plugin should have a static constructor
+ // that registers that class with the Core.DeviceFactory
+ Debug.Console(0, "Done Loading Custom Plugin Types.");
+ }
+
+ ///
+ /// Loads plugins
+ ///
+ 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();
+
+ if(Directory.Exists(_loadedPluginsDirectoryPath)) {
+ // Load the assemblies from the loadedPlugins folder into the AppDomain
+ LoadPluginAssemblies();
+
+ // Load the types from any custom plugin assemblies
+ LoadCustomPluginTypes();
+ }
+ }
+ }
+ }
+
+ ///
+ /// Represents an assembly loaded at runtime and it's associated metadata
+ ///
+ public class LoadedAssembly
+ {
+ public string Name { get; private set; }
+ public string Version { get; private set; }
+ public Assembly Assembly { get; private set; }
+
+ public LoadedAssembly(string name, string version, Assembly assembly)
+ {
+ Name = name;
+ Version = version;
+ Assembly = assembly;
+ }
+ }
+}
\ No newline at end of file