mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-02-15 12:44:58 +00:00
Moves all plugin logic to new PluginLoader static class
This commit is contained in:
@@ -25,7 +25,6 @@ namespace PepperDash.Essentials
|
|||||||
{
|
{
|
||||||
HttpLogoServer LogoServer;
|
HttpLogoServer LogoServer;
|
||||||
|
|
||||||
List<string> LoadedAssemblies = new List<string>();
|
|
||||||
|
|
||||||
public ControlSystem()
|
public ControlSystem()
|
||||||
: base()
|
: base()
|
||||||
@@ -48,6 +47,8 @@ namespace PepperDash.Essentials
|
|||||||
ConsoleAccessLevelEnum.AccessOperator);
|
ConsoleAccessLevelEnum.AccessOperator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CrestronConsole.AddNewConsoleCommand(PluginLoader.ReportAssemblyVersions, "reportversions", "Reports the versions of the loaded assemblies", ConsoleAccessLevelEnum.AccessOperator);
|
||||||
|
|
||||||
// CrestronConsole.AddNewConsoleCommand(S => { ConfigWriter.WriteConfigFile(null); }, "writeconfig", "writes the current config to a file", ConsoleAccessLevelEnum.AccessOperator);
|
// CrestronConsole.AddNewConsoleCommand(S => { ConfigWriter.WriteConfigFile(null); }, "writeconfig", "writes the current config to a file", ConsoleAccessLevelEnum.AccessOperator);
|
||||||
CrestronConsole.AddNewConsoleCommand(s =>
|
CrestronConsole.AddNewConsoleCommand(s =>
|
||||||
{
|
{
|
||||||
@@ -80,6 +81,8 @@ namespace PepperDash.Essentials
|
|||||||
GoWithLoad();
|
GoWithLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if the program is running on a processor (appliance) or server (VC-4).
|
/// Determines if the program is running on a processor (appliance) or server (VC-4).
|
||||||
///
|
///
|
||||||
@@ -160,20 +163,13 @@ namespace PepperDash.Essentials
|
|||||||
|
|
||||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials load from configuration");
|
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials load from configuration");
|
||||||
|
|
||||||
// Get the loaded assembly filenames
|
PluginLoader.AddProgramAssemblies();
|
||||||
var appDi = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix);
|
|
||||||
var assemblyFiles = appDi.GetFiles("*.dll");
|
|
||||||
|
|
||||||
foreach (var file in assemblyFiles)
|
|
||||||
{
|
|
||||||
LoadedAssemblies.Add(file.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
var filesReady = SetupFilesystem();
|
var filesReady = SetupFilesystem();
|
||||||
if (filesReady)
|
if (filesReady)
|
||||||
{
|
{
|
||||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Checking for plugins");
|
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Checking for plugins");
|
||||||
LoadPlugins();
|
PluginLoader.LoadPlugins();
|
||||||
|
|
||||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Folder structure verified. Loading config...");
|
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Folder structure verified. Loading config...");
|
||||||
if (!ConfigReader.LoadConfig2())
|
if (!ConfigReader.LoadConfig2())
|
||||||
@@ -208,142 +204,7 @@ namespace PepperDash.Essentials
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loads plugins
|
|
||||||
/// </summary>
|
|
||||||
void LoadPlugins()
|
|
||||||
{
|
|
||||||
var dir = Global.FilePathPrefix + "plugins";
|
|
||||||
if (Directory.Exists(dir))
|
|
||||||
{
|
|
||||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Plugins directory found, checking for factory plugins");
|
|
||||||
var di = new DirectoryInfo(dir);
|
|
||||||
var zFiles = di.GetFiles("*.cplz");
|
|
||||||
foreach (var fi in zFiles)
|
|
||||||
{
|
|
||||||
Debug.Console(0, "Found cplz: {0}. Unzipping into plugins directory", fi.Name);
|
|
||||||
var result = CrestronZIP.Unzip(fi.FullName, di.FullName);
|
|
||||||
Debug.Console(0, "UnZip Result: {0}", result.ToString());
|
|
||||||
fi.Delete();
|
|
||||||
}
|
|
||||||
var files = di.GetFiles("*.dll");
|
|
||||||
Dictionary<string, Assembly> assyList = new Dictionary<string, Assembly>();
|
|
||||||
|
|
||||||
foreach (FileInfo fi in files)
|
|
||||||
{
|
|
||||||
if (!CheckIfAssemblyExists(fi.Name))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string assyVersion = string.Empty;
|
|
||||||
var assy = Assembly.LoadFrom(fi.FullName);
|
|
||||||
var ver = assy.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
|
|
||||||
if (ver != null)
|
|
||||||
{
|
|
||||||
AssemblyInformationalVersionAttribute verAttribute = ver[0] as AssemblyInformationalVersionAttribute;
|
|
||||||
assyVersion = verAttribute.InformationalVersion;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
|
|
||||||
|
|
||||||
assyList.Add(fi.FullName, assy);
|
|
||||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loaded plugin file '{0}', version {1}", fi.FullName, assyVersion);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Debug.Console(2, "Assembly {0} is not a custom assembly", fi.FullName);
|
|
||||||
continue; //catching any load issues and continuing. There will be exceptions loading Crestron .dlls from the cplz Probably should do something different here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Skipping plugin: {0}. There is already an assembly with that name loaded.", fi.Name);
|
|
||||||
}
|
|
||||||
foreach (var assy in assyList)
|
|
||||||
{
|
|
||||||
// iteratate this assembly's classes, looking for "LoadPlugin()" methods
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var types = assy.Value.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}", assy.Key);
|
|
||||||
loadPlugin.Invoke(null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Debug.Console(2, "Load Plugin not found. {0} is not a plugin assembly", assy.Value.FullName);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Debug.Console(2, "Assembly {0} is not a custom assembly. Types cannot be loaded.", assy.Value.FullName);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// plugin dll will be loaded. Any classes in plugin should have a static constructor
|
|
||||||
// that registers that class with the Core.DeviceFactory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the filename matches an already loaded assembly file's name
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filename"></param>
|
|
||||||
/// <returns>True if file already matches loaded assembly file.</returns>
|
|
||||||
bool CheckIfAssemblyExists(string filename)
|
|
||||||
{
|
|
||||||
var loadedAssembly = LoadedAssemblies.FirstOrDefault(s => s.Equals(filename));
|
|
||||||
|
|
||||||
if (loadedAssembly != null)
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verifies filesystem is set up. IR, SGD, and programX folders
|
/// Verifies filesystem is set up. IR, SGD, and programX folders
|
||||||
|
|||||||
@@ -1,266 +0,0 @@
|
|||||||
//using System;
|
|
||||||
//using System.Collections.Generic;
|
|
||||||
//using System.Linq;
|
|
||||||
//using System.Text;
|
|
||||||
//using Crestron.SimplSharp;
|
|
||||||
//using Crestron.SimplSharp.CrestronIO;
|
|
||||||
//using Crestron.SimplSharp.Net.Http;
|
|
||||||
|
|
||||||
//using Newtonsoft.Json;
|
|
||||||
//using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
//using PepperDash.Essentials.Core;
|
|
||||||
//using PepperDash.Essentials.Core.Http;
|
|
||||||
//using PepperDash.Core;
|
|
||||||
|
|
||||||
//namespace PepperDash.Essentials
|
|
||||||
//{
|
|
||||||
// public class EssentialsHttpApiHandler
|
|
||||||
// {
|
|
||||||
// string ConfigPath;
|
|
||||||
// string PresetsPathPrefix;
|
|
||||||
// EssentialsHttpServer Server;
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// ///
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="server">HTTP server to attach to</param>
|
|
||||||
// /// <param name="configPath">The full path to configuration file</param>
|
|
||||||
// /// <param name="presetsListPath">The folder prefix for the presets path, eq "\HTML\presets\"</param>
|
|
||||||
// public EssentialsHttpApiHandler(EssentialsHttpServer server, string configPath, string presetsPathPrefix)
|
|
||||||
// {
|
|
||||||
// if (server == null) throw new ArgumentNullException("server");
|
|
||||||
// Server = server;
|
|
||||||
// ConfigPath = configPath;
|
|
||||||
// PresetsPathPrefix = presetsPathPrefix;
|
|
||||||
// server.ApiRequest += Server_ApiRequest;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// void Server_ApiRequest(object sender, Crestron.SimplSharp.Net.Http.OnHttpRequestArgs args)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// var path = args.Request.Path.ToLower();
|
|
||||||
|
|
||||||
// if (path == "/api/config")
|
|
||||||
// HandleApiConfig(args);
|
|
||||||
// else if (path.StartsWith("/api/presetslist/"))
|
|
||||||
// HandleApiPresetsList(args);
|
|
||||||
// else if (path == "/api/presetslists")
|
|
||||||
// HandleApiGetPresetsLists(args.Request, args.Response);
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// args.Response.Code = 404;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// args.Response.Header.SetHeaderValue("Access-Control-Allow-Origin", "*");
|
|
||||||
// args.Response.Header.SetHeaderValue("Access-Control-Allow-Methods", "GET, POST, PATCH, PUT, DELETE, OPTIONS");
|
|
||||||
// }
|
|
||||||
// catch (Exception e)
|
|
||||||
// {
|
|
||||||
// Debug.Console(1, "Uncaught HTTP server error: \n{0}", e);
|
|
||||||
// args.Response.Code = 500;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// GET will return the running configuration. POST will attempt to take in a new config
|
|
||||||
// /// and restart the program.
|
|
||||||
// /// </summary>
|
|
||||||
// void HandleApiConfig(OnHttpRequestArgs args)
|
|
||||||
// {
|
|
||||||
// var request = args.Request;
|
|
||||||
// if (request.Header.RequestType == "GET")
|
|
||||||
// {
|
|
||||||
// if (File.Exists(ConfigPath))
|
|
||||||
// {
|
|
||||||
// Debug.Console(2, "Sending config:{0}", ConfigPath);
|
|
||||||
// args.Response.Header.ContentType = EssentialsHttpServer.GetContentType(new FileInfo(ConfigPath).Extension);
|
|
||||||
// args.Response.ContentStream = new FileStream(ConfigPath, FileMode.Open, FileAccess.Read);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else if (request.Header.RequestType == "POST")
|
|
||||||
// {
|
|
||||||
// Debug.Console(2, "Post type: '{0}'", request.Header.ContentType);
|
|
||||||
|
|
||||||
// // Make sure we're receiving at least good json
|
|
||||||
// Debug.Console(1, "Receving new config");
|
|
||||||
// if (GetContentStringJson(args) == null)
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// //---------------------------- try to move these into common method
|
|
||||||
// // Move current file aside
|
|
||||||
// var bakPath = ConfigPath + ".bak";
|
|
||||||
// if (File.Exists(bakPath))
|
|
||||||
// File.Delete(bakPath);
|
|
||||||
// File.Move(ConfigPath, bakPath);
|
|
||||||
|
|
||||||
// // Write the file
|
|
||||||
// using (FileStream fs = File.Open(ConfigPath, FileMode.OpenOrCreate))
|
|
||||||
// using (StreamWriter sw = new StreamWriter(fs))
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// sw.Write(args.Request.ContentString);
|
|
||||||
// }
|
|
||||||
// catch (Exception e)
|
|
||||||
// {
|
|
||||||
// string err = string.Format("Error writing received config file:\r{0}", e);
|
|
||||||
// CrestronConsole.PrintLine(err);
|
|
||||||
// ErrorLog.Warn(err);
|
|
||||||
// // Put file back
|
|
||||||
// File.Move(ConfigPath + ".bak", ConfigPath);
|
|
||||||
// args.Response.Code = 500;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // If client says "yeah, restart" and has a good token
|
|
||||||
// // Restart program
|
|
||||||
// string consoleResponse = null;
|
|
||||||
// var restart = CrestronConsole.SendControlSystemCommand("progreset -p:" +
|
|
||||||
// InitialParametersClass.ApplicationNumber, ref consoleResponse);
|
|
||||||
// if (!restart) Debug.Console(0, "CAN'T DO THAT YO: {0}", consoleResponse);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void HandleApiPresetsList(OnHttpRequestArgs args)
|
|
||||||
// {
|
|
||||||
// var listPath = PresetsPathPrefix + args.Request.Path.Remove(0, 17);
|
|
||||||
// Debug.Console(2, "Checking for preset list '{0}'", listPath);
|
|
||||||
|
|
||||||
// if (args.Request.Header.RequestType == "GET")
|
|
||||||
// {
|
|
||||||
// if (File.Exists(listPath))
|
|
||||||
// {
|
|
||||||
// Debug.Console(2, "Sending presets file:{0}", listPath);
|
|
||||||
// args.Response.Header.ContentType = EssentialsHttpServer.GetContentType(new FileInfo(listPath).Extension);
|
|
||||||
// args.Response.ContentStream = new FileStream(listPath, FileMode.Open, FileAccess.Read);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else if (args.Request.Header.RequestType == "POST")
|
|
||||||
// {
|
|
||||||
// // Make sure we're receiving at least good json
|
|
||||||
// Debug.Console(1, "Receving new presets");
|
|
||||||
// if (GetContentStringJson(args) == null)
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// //---------------------------- try to move these into common method
|
|
||||||
// // Move current file aside
|
|
||||||
// var bakPath = listPath + ".new";
|
|
||||||
// Debug.Console(2, "Moving presets file to {0}", bakPath);
|
|
||||||
// if(File.Exists(bakPath))
|
|
||||||
// File.Delete(bakPath);
|
|
||||||
// File.Move(listPath, bakPath);
|
|
||||||
|
|
||||||
// Debug.Console(2, "Writing new file");
|
|
||||||
// // Write the file
|
|
||||||
// using (FileStream fs = File.OpenWrite(listPath))
|
|
||||||
// using (StreamWriter sw = new StreamWriter(fs))
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// Debug.Console(2, "Writing {1}, {0} bytes", args.Request.ContentString.Length, listPath);
|
|
||||||
// sw.Write(args.Request.ContentString);
|
|
||||||
// }
|
|
||||||
// catch (Exception e)
|
|
||||||
// {
|
|
||||||
// string err = string.Format("Error writing received presets file:\r{0}", e);
|
|
||||||
// CrestronConsole.PrintLine(err);
|
|
||||||
// ErrorLog.Warn(err);
|
|
||||||
// // Put file back
|
|
||||||
// File.Move(listPath + ".bak", listPath);
|
|
||||||
// args.Response.Code = 500;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// void HandleApiGetPresetsLists(HttpServerRequest request, HttpServerResponse response)
|
|
||||||
// {
|
|
||||||
// if (request.Header.RequestType != "GET")
|
|
||||||
// {
|
|
||||||
// response.Code = 404; // This should be a 405 with an allow header
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (Directory.Exists(PresetsPathPrefix))
|
|
||||||
// {
|
|
||||||
// //CrestronConsole.PrintLine("Parsing presets directory");
|
|
||||||
// List<string> files = Directory.GetFiles(PresetsPathPrefix, "*.json")
|
|
||||||
// .ToList().Select(f => Path.GetFileName(f)).ToList();
|
|
||||||
// if (files.Count > 0)
|
|
||||||
// files.Sort();
|
|
||||||
// var json = JsonConvert.SerializeObject(files);
|
|
||||||
// response.Header.ContentType = "application/json";
|
|
||||||
// response.ContentString = json;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // //CrestronConsole.PrintLine("Found {0} files", files.Count);
|
|
||||||
// // JObject jo = new JObject();
|
|
||||||
// // JArray ja = new JArray();
|
|
||||||
|
|
||||||
// // foreach (var filename in files)
|
|
||||||
// // {
|
|
||||||
// // try
|
|
||||||
// // {
|
|
||||||
// // using (StreamReader sr = new StreamReader(filename))
|
|
||||||
// // {
|
|
||||||
// // JObject tempJo = JObject.Parse(sr.ReadToEnd());
|
|
||||||
// // if (tempJo.Value<string>("content").Equals("presetsList"))
|
|
||||||
// // {
|
|
||||||
// // var jItem = new JObject(); // make a new object
|
|
||||||
// // jItem.Add("Name", tempJo["name"]);
|
|
||||||
// // jItem.Add("File", filename);
|
|
||||||
// // jItem.Add("Url", Uri.EscapeUriString(new Uri(
|
|
||||||
// // filename.Replace("\\html", "")
|
|
||||||
// // .Replace("\\HTML", "")
|
|
||||||
// // .Replace('\\', '/'), UriKind.Relative).ToString()));
|
|
||||||
// // ja.Add(jItem); // add to array
|
|
||||||
// // }
|
|
||||||
// // else
|
|
||||||
// // CrestronConsole.PrintLine("Cannot use presets file '{0}'", filename);
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// // catch
|
|
||||||
// // {
|
|
||||||
// // // ignore failures - maybe delete them
|
|
||||||
// // CrestronConsole.PrintLine("Unable to read presets file '{0}'", filename);
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// // jo.Add("PresetChannelLists", ja);
|
|
||||||
// // //CrestronConsole.PrintLine(jo.ToString());
|
|
||||||
// // response.Header.ContentType = "application/json";
|
|
||||||
// // response.ContentString = jo.ToString();
|
|
||||||
// //}
|
|
||||||
// //else
|
|
||||||
// // CrestronConsole.PrintLine("No presets files in directory");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// Simply does what it says
|
|
||||||
// /// </summary>
|
|
||||||
// JObject GetContentStringJson(OnHttpRequestArgs args)
|
|
||||||
// {
|
|
||||||
// //var content = args.Request.ContentString;
|
|
||||||
// //Debug.Console(1, "{0}", content);
|
|
||||||
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// // just see if it parses properly
|
|
||||||
// return JObject.Parse(args.Request.ContentString);
|
|
||||||
// }
|
|
||||||
// catch (Exception e)
|
|
||||||
// {
|
|
||||||
// string err = string.Format("JSON Error reading config file:\r{0}", e);
|
|
||||||
// CrestronConsole.PrintLine(err);
|
|
||||||
// ErrorLog.Warn(err);
|
|
||||||
// args.Response.Code = 400; // Bad request
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
<Compile Include="ControlSystem.cs" />
|
<Compile Include="ControlSystem.cs" />
|
||||||
<Compile Include="Factory\UiDeviceFactory.cs" />
|
<Compile Include="Factory\UiDeviceFactory.cs" />
|
||||||
<Compile Include="Fusion\EssentialsHuddleVtc1FusionController.cs" />
|
<Compile Include="Fusion\EssentialsHuddleVtc1FusionController.cs" />
|
||||||
<Compile Include="HttpApiHandler.cs" />
|
<Compile Include="PluginLoading\PluginLoading.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Room\Config\EssentialsDualDisplayRoomPropertiesConfig.cs" />
|
<Compile Include="Room\Config\EssentialsDualDisplayRoomPropertiesConfig.cs" />
|
||||||
<Compile Include="Room\Config\EssentialsNDisplayRoomPropertiesConfig.cs" />
|
<Compile Include="Room\Config\EssentialsNDisplayRoomPropertiesConfig.cs" />
|
||||||
|
|||||||
287
PepperDashEssentials/PluginLoading/PluginLoading.cs
Normal file
287
PepperDashEssentials/PluginLoading/PluginLoading.cs
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Deals with loading plugins at runtime
|
||||||
|
/// </summary>
|
||||||
|
public static class PluginLoader
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The complete list of loaded assemblies. Includes Essentials Framework assemblies and plugins
|
||||||
|
/// </summary>
|
||||||
|
public static List<LoadedAssembly> LoadedAssemblies { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of assemblies loaded from the plugins folder
|
||||||
|
/// </summary>
|
||||||
|
static List<LoadedAssembly> LoadedPluginFolderAssemblies;
|
||||||
|
|
||||||
|
static PluginLoader()
|
||||||
|
{
|
||||||
|
LoadedAssemblies = new List<LoadedAssembly>();
|
||||||
|
LoadedPluginFolderAssemblies = new List<LoadedAssembly>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves all the loaded assemblies from the program directory
|
||||||
|
/// </summary>
|
||||||
|
public static void AddProgramAssemblies()
|
||||||
|
{
|
||||||
|
// Get the loaded assembly filenames
|
||||||
|
var appDi = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix);
|
||||||
|
var assemblyFiles = appDi.GetFiles("*.dll");
|
||||||
|
|
||||||
|
foreach (var file in assemblyFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LoadAssembly(file.FullName);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Assembly {0} is not a custom assembly", file.FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads an assembly via Reflection and adds it to the list of loaded assemblies
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName"></param>
|
||||||
|
static LoadedAssembly LoadAssembly(string fileName)
|
||||||
|
{
|
||||||
|
if (!CheckIfAssemblyExists(fileName))
|
||||||
|
{
|
||||||
|
|
||||||
|
var assembly = Assembly.LoadFrom(fileName);
|
||||||
|
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 plugin file '{0}', version {1}", loadedAssembly.FileName, loadedAssembly.Version);
|
||||||
|
return loadedAssembly;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Unable to load assembly: '{0}'", fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Skipping assembly: {0}. There is already an assembly with that name loaded.", fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to get the assembly informational version and if not possible gets the version
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assembly"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
static string GetAssemblyVersion(Assembly assembly)
|
||||||
|
{
|
||||||
|
var ver = assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
|
||||||
|
if (ver != null)
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the filename matches an already loaded assembly file's name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename"></param>
|
||||||
|
/// <returns>True if file already matches loaded assembly file.</returns>
|
||||||
|
public static bool CheckIfAssemblyExists(string filename)
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Checking if assembly: {0} is loaded...", filename);
|
||||||
|
var loadedAssembly = LoadedAssemblies.FirstOrDefault(s => s.FileName.Equals(filename));
|
||||||
|
|
||||||
|
if (loadedAssembly != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used by console command to report the currently loaded assemblies and versions
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command"></param>
|
||||||
|
public static void ReportAssemblyVersions(string command)
|
||||||
|
{
|
||||||
|
Debug.Console(0, "Loaded Assemblies:");
|
||||||
|
foreach (var assembly in LoadedAssemblies)
|
||||||
|
{
|
||||||
|
Debug.Console(0, "{0} Version: {1}", assembly.FileName, assembly.Version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads plugins
|
||||||
|
/// </summary>
|
||||||
|
public static void LoadPlugins()
|
||||||
|
{
|
||||||
|
var dir = Global.FilePathPrefix + "plugins";
|
||||||
|
var tempDir = dir + Global.DirectorySeparator + "temp";
|
||||||
|
|
||||||
|
if (Directory.Exists(dir))
|
||||||
|
{
|
||||||
|
Debug.Console(0, Debug.ErrorLogLevel.Notice, "Plugins directory found, checking for factory plugins");
|
||||||
|
var di = new DirectoryInfo(dir);
|
||||||
|
var zFiles = di.GetFiles("*.cplz");
|
||||||
|
|
||||||
|
foreach (var fi in zFiles)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
var tempDi = new DirectoryInfo(tempDir);
|
||||||
|
|
||||||
|
Debug.Console(0, "Found cplz: {0}. Unzipping into temp plugins directory", fi.Name);
|
||||||
|
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
|
||||||
|
{
|
||||||
|
var pluginAssembly = LoadAssembly(fi.FullName);
|
||||||
|
|
||||||
|
if (pluginAssembly != null)
|
||||||
|
{
|
||||||
|
LoadedPluginFolderAssemblies.Add(pluginAssembly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Assembly {0} is not a custom assembly", file.FullName);
|
||||||
|
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
|
||||||
|
fi.Delete();
|
||||||
|
Directory.Delete(tempDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate the loaded assemblies and try to call the LoadPlugin method
|
||||||
|
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.FileName);
|
||||||
|
loadPlugin.Invoke(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Load Plugin not found. {0} is not a plugin assembly", loadedAssembly.FileName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Debug.Console(2, "Assembly {0} is not a custom assembly. Types cannot be loaded.", loadedAssembly.FileName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// plugin dll will be loaded. Any classes in plugin should have a static constructor
|
||||||
|
// that registers that class with the Core.DeviceFactory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an assembly loaded at runtime and it's associated metadata
|
||||||
|
/// </summary>
|
||||||
|
public class LoadedAssembly
|
||||||
|
{
|
||||||
|
public string FileName { get; private set; }
|
||||||
|
public string Version { get; private set; }
|
||||||
|
public Assembly Assembly { get; private set; }
|
||||||
|
|
||||||
|
public LoadedAssembly(string fileName, string version, Assembly assembly)
|
||||||
|
{
|
||||||
|
FileName = fileName;
|
||||||
|
Version = version;
|
||||||
|
Assembly = assembly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,15 +62,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
FilePathPrefix = prefix;
|
FilePathPrefix = prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the file path prefix
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="prefix"></param>
|
|
||||||
public static void SetApplicationDirectoryPathPrefix(string prefix)
|
|
||||||
{
|
|
||||||
ApplicationDirectoryPathPrefix = prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
static string _AssemblyVersion;
|
static string _AssemblyVersion;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user