diff --git a/PepperDashEssentials/ControlSystem.cs b/PepperDashEssentials/ControlSystem.cs index e052f274..46ac77f2 100644 --- a/PepperDashEssentials/ControlSystem.cs +++ b/PepperDashEssentials/ControlSystem.cs @@ -25,6 +25,7 @@ namespace PepperDash.Essentials { HttpLogoServer LogoServer; + public ControlSystem() : base() { @@ -46,6 +47,8 @@ namespace PepperDash.Essentials 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 => { @@ -78,10 +81,12 @@ namespace PepperDash.Essentials GoWithLoad(); } + + /// /// Determines if the program is running on a processor (appliance) or server (VC-4). /// - /// Sets Global.FilePathPrefix based on platform + /// Sets Global.FilePathPrefix and Global.ApplicationDirectoryPathPrefix based on platform /// public void DeterminePlatform() { @@ -108,7 +113,7 @@ namespace PepperDash.Essentials Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials v{0} on 3-series Appliance", Global.AssemblyVersion); // Check if User/ProgramX exists - if (Directory.Exists(directoryPrefix + dirSeparator + "User" + if (Directory.Exists(Global.ApplicationDirectoryPathPrefix + dirSeparator + "User" + dirSeparator + string.Format("program{0}", InitialParametersClass.ApplicationNumber))) { Debug.Console(0, @"User/program{0} directory found", InitialParametersClass.ApplicationNumber); @@ -158,11 +163,13 @@ namespace PepperDash.Essentials Debug.Console(0, Debug.ErrorLogLevel.Notice, "Starting Essentials load from configuration"); + PluginLoader.AddProgramAssemblies(); + var filesReady = SetupFilesystem(); if (filesReady) { Debug.Console(0, Debug.ErrorLogLevel.Notice, "Checking for plugins"); - LoadPlugins(); + PluginLoader.LoadPlugins(); Debug.Console(0, Debug.ErrorLogLevel.Notice, "Folder structure verified. Loading config..."); if (!ConfigReader.LoadConfig2()) @@ -197,118 +204,7 @@ namespace PepperDash.Essentials } - /// - /// Initial simple implementation. Reads user/programXX/plugins folder and - /// use - /// - void LoadPlugins() - { - var dir = Global.FilePathPrefix + "plugins"; - if (Directory.Exists(dir)) - { - // TODO Clear out or create localPlugins folder (maybe in program slot folder) - - 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 assyList = new Dictionary(); - foreach (FileInfo fi in files) - { - // TODO COPY plugin to loadedPlugins folder - // TODO LOAD that loadedPlugins dll file - try - { - var assy = Assembly.LoadFrom(fi.FullName); - var ver = assy.GetName().Version; - var verStr = string.Format("{0}.{1}.{2}.{3}", ver.Major, ver.Minor, ver.Build, ver.Revision); - assyList.Add(fi.FullName, assy); - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loaded plugin file '{0}', version {1}", fi.FullName, verStr); - } - 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 - } - } - 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 - } - } + /// /// Verifies filesystem is set up. IR, SGD, and programX folders diff --git a/PepperDashEssentials/HttpApiHandler.cs b/PepperDashEssentials/HttpApiHandler.cs deleted file mode 100644 index 0a757999..00000000 --- a/PepperDashEssentials/HttpApiHandler.cs +++ /dev/null @@ -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; - -// /// -// /// -// /// -// /// HTTP server to attach to -// /// The full path to configuration file -// /// The folder prefix for the presets path, eq "\HTML\presets\" -// 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; -// } -// } - -// /// -// /// GET will return the running configuration. POST will attempt to take in a new config -// /// and restart the program. -// /// -// 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 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("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"); -// } - -// /// -// /// Simply does what it says -// /// -// 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; -// } -// } -// } -//} \ No newline at end of file diff --git a/PepperDashEssentials/PepperDashEssentials.csproj b/PepperDashEssentials/PepperDashEssentials.csproj index 612bdf10..0bd07ecf 100644 --- a/PepperDashEssentials/PepperDashEssentials.csproj +++ b/PepperDashEssentials/PepperDashEssentials.csproj @@ -166,7 +166,7 @@ - + diff --git a/PepperDashEssentials/PluginLoading/PluginLoading.cs b/PepperDashEssentials/PluginLoading/PluginLoading.cs new file mode 100644 index 00000000..032bea2e --- /dev/null +++ b/PepperDashEssentials/PluginLoading/PluginLoading.cs @@ -0,0 +1,444 @@ +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(); + + // 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 diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/ConfigReader.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/ConfigReader.cs index 0a288944..b3fc7d89 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/ConfigReader.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/ConfigReader.cs @@ -64,16 +64,23 @@ namespace PepperDash.Essentials.Core.Config if (configFiles != null) { + Debug.Console(2, "{0} config files found matching pattern", configFiles.Length); + if (configFiles.Length > 1) { Debug.Console(0, Debug.ErrorLogLevel.Error, "****Error: Multiple Portal Configuration files present. Please ensure only a single file exists and reset program.****"); return false; } - else + else if (configFiles.Length == 1) { 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 { diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Global/Global.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Global/Global.cs index 4ef68066..de533ebb 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Global/Global.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Global/Global.cs @@ -98,7 +98,7 @@ namespace PepperDash.Essentials.Core { 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 runtimeVersionMinor = Int16.Parse(runtimeVersion.Groups[2].Value); @@ -107,7 +107,7 @@ namespace PepperDash.Essentials.Core // Check for beta build version if (runtimeVersionMajor == 0) { - Debug.Console(2, "Running Beta Build. Bypassing Dependency Check."); + Debug.Console(2, "Running Local Build. Bypassing Dependency Check."); return true; } diff --git a/essentials-framework/pepperdashcore-builds b/essentials-framework/pepperdashcore-builds index acebe6b4..15206840 160000 --- a/essentials-framework/pepperdashcore-builds +++ b/essentials-framework/pepperdashcore-builds @@ -1 +1 @@ -Subproject commit acebe6b43b28cc3a93f899e9714292a0cc1ab2cc +Subproject commit 15206840b3e6338f695e4ffba634a72e51ea1be5