diff --git a/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs b/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs index deec08cb..c3259305 100644 --- a/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs +++ b/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs @@ -4,18 +4,15 @@ using System.Linq; using Crestron.SimplSharp; using System.Reflection; using System.IO; -using Newtonsoft.Json; using System.Reflection.PortableExecutable; using System.Reflection.Metadata; -using SystemIO = System.IO; -using CrestronIO = System.IO; using PepperDash.Core; using PepperDash.Essentials.Core; using Serilog.Events; -namespace PepperDash.Essentials; +namespace PepperDash.Essentials; /// /// Provides functionality for loading and managing plugins and assemblies in the application. /// @@ -75,19 +72,6 @@ public static class PluginLoader /// private static string TempDirectory => PluginDirectory + Global.DirectorySeparator + "temp"; - /// - /// 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"; - /// /// Represents a collection of fully qualified type names that are known to be incompatible with the current /// application or framework. @@ -96,8 +80,8 @@ public static class PluginLoader /// incompatible with the intended usage of the application. These types may represent security risks, unsupported /// features, or legacy APIs that should be avoided. private static readonly HashSet KnownIncompatibleTypes = - [ - "System.Net.ICertificatePolicy", + [ + "System.Net.ICertificatePolicy", "System.Security.Cryptography.SHA1CryptoServiceProvider", "System.Web.HttpUtility", "System.Configuration.ConfigurationManager", @@ -108,7 +92,7 @@ public static class PluginLoader "System.Security.SecurityManager", "System.Security.Permissions.FileIOPermission", "System.AppDomain.CreateDomain" - ]; + ]; /// /// Initializes static members of the class. @@ -142,7 +126,6 @@ public static class PluginLoader Debug.LogMessage(LogEventLevel.Verbose, "Found {0} Assemblies", assemblyFiles.Length); - foreach (var fi in assemblyFiles.Where(fi => fi.Name.Contains("Essentials") || fi.Name.Contains("PepperDash"))) { string version = string.Empty; @@ -190,7 +173,6 @@ public static class PluginLoader } } - /// /// Associates the specified assembly with the given name in the loaded assemblies collection. /// @@ -314,7 +296,7 @@ public static class PluginLoader { string fileName = Path.GetFileName(filePath); Debug.LogMessage(LogEventLevel.Warning, "Assembly '{0}' is not compatible with .NET 8: {1}", fileName, reason); - + var incompatiblePlugin = new IncompatiblePlugin(fileName, reason, requestedBy); IncompatiblePlugins.Add(incompatiblePlugin); return null; @@ -335,15 +317,15 @@ public static class PluginLoader } return null; } - catch (FileLoadException ex) when (ex.Message.Contains("Assembly with same name is already loaded")) + catch(FileLoadException ex) when (ex.Message.Contains("Assembly with same name is already loaded")) { // Get the assembly name from the file path string assemblyName = Path.GetFileNameWithoutExtension(filePath); - + // Try to find the already loaded assembly var existingAssembly = AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(a => a.GetName().Name.Equals(assemblyName, StringComparison.OrdinalIgnoreCase)); - + if (existingAssembly != null) { Debug.LogMessage(LogEventLevel.Information, "Assembly '{0}' is already loaded, using existing instance", assemblyName); @@ -352,19 +334,19 @@ public static class PluginLoader LoadedAssemblies.Add(loadedAssembly); return loadedAssembly; } - + Debug.LogMessage(LogEventLevel.Warning, "Assembly with same name already loaded but couldn't find it: {0}", filePath); return null; } - catch (Exception ex) + catch(Exception ex) { string fileName = Path.GetFileName(filePath); - + // Check if this might be a .NET Framework compatibility issue - if (ex.Message.Contains("Could not load type") || + if (ex.Message.Contains("Could not load type") || ex.Message.Contains("Unable to load one or more of the requested types")) { - Debug.LogMessage(LogEventLevel.Error, "Error loading assembly {0}: Likely .NET 8 compatibility issue: {1}", + Debug.LogMessage(LogEventLevel.Error, "Error loading assembly {0}: Likely .NET 8 compatibility issue: {1}", fileName, ex.Message); IncompatiblePlugins.Add(new IncompatiblePlugin(fileName, ex.Message, requestedBy)); } @@ -454,12 +436,12 @@ public static class PluginLoader { if (plugin.TriggeredBy != "Direct load") { - CrestronConsole.ConsoleCommandResponse("{0}: {1} (Required by: {2})" + CrestronEnvironment.NewLine, + CrestronConsole.ConsoleCommandResponse("{0}: {1} (Required by: {2})" + CrestronEnvironment.NewLine, plugin.Name, plugin.Reason, plugin.TriggeredBy); } else { - CrestronConsole.ConsoleCommandResponse("{0}: {1}" + CrestronEnvironment.NewLine, + CrestronConsole.ConsoleCommandResponse("{0}: {1}" + CrestronEnvironment.NewLine, plugin.Name, plugin.Reason); } } @@ -626,29 +608,29 @@ public static class PluginLoader // First, check compatibility of all assemblies before loading any var assemblyCompatibility = new Dictionary References)>(); - + foreach (var pluginFile in pluginFiles) { string fileName = pluginFile.Name; assemblyCompatibility[fileName] = IsPluginCompatibleWithNet8(pluginFile.FullName); } - + // Now load compatible assemblies and track incompatible ones foreach (var pluginFile in pluginFiles) { string fileName = pluginFile.Name; var (isCompatible, reason, _) = assemblyCompatibility[fileName]; - + if (!isCompatible) { Debug.LogMessage(LogEventLevel.Warning, "Assembly '{0}' is not compatible with .NET 8: {1}", fileName, reason); IncompatiblePlugins.Add(new IncompatiblePlugin(fileName, reason, null)); continue; } - + // Try to load the assembly var loadedAssembly = LoadAssembly(pluginFile.FullName, null); - + if (loadedAssembly != null) { LoadedPluginFolderAssemblies.Add(loadedAssembly); @@ -668,13 +650,13 @@ public static class PluginLoader private static void LoadCustomPluginTypes() { Debug.LogMessage(LogEventLevel.Information, "Loading Custom Plugin Types..."); - + foreach (var loadedAssembly in LoadedPluginFolderAssemblies) { // Skip if assembly is null (can happen if we had loading issues) if (loadedAssembly == null || loadedAssembly.Assembly == null) continue; - + // iteratate this assembly's classes, looking for "LoadPlugin()" methods try { @@ -689,7 +671,7 @@ public static class PluginLoader { Debug.LogMessage(LogEventLevel.Error, "Unable to get types for assembly {0}: {1}", loadedAssembly.Name, e.Message); - + // Check if any of the loader exceptions are due to missing assemblies foreach (var loaderEx in e.LoaderExceptions) { @@ -698,18 +680,18 @@ public static class PluginLoader string missingAssembly = fileNotFoundEx.FileName; if (!string.IsNullOrEmpty(missingAssembly)) { - Debug.LogMessage(LogEventLevel.Warning, "Assembly {0} requires missing dependency: {1}", + Debug.LogMessage(LogEventLevel.Warning, "Assembly {0} requires missing dependency: {1}", loadedAssembly.Name, missingAssembly); - + // Add to incompatible plugins with dependency information IncompatiblePlugins.Add(new IncompatiblePlugin( - Path.GetFileName(missingAssembly), - $"Missing dependency required by {loadedAssembly.Name}", + Path.GetFileName(missingAssembly), + $"Missing dependency required by {loadedAssembly.Name}", loadedAssembly.Name)); } } } - + Debug.LogMessage(LogEventLevel.Verbose, e.StackTrace); continue; } @@ -718,18 +700,19 @@ public static class PluginLoader Debug.LogMessage(LogEventLevel.Error, "Unable to get types for assembly {0}: {1}", loadedAssembly.Name, e.Message); Debug.LogMessage(LogEventLevel.Verbose, e.StackTrace); - + // Add to incompatible plugins if this is likely a .NET 8 compatibility issue - if (e.Message.Contains("Could not load type") || + if (e.Message.Contains("Could not load type") || e.Message.Contains("Unable to load one or more of the requested types")) { - IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name, - $"Type loading error: {e.Message}", + IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name, + $"Type loading error: {e.Message}", null)); } - + continue; } + foreach (var type in types) { try @@ -759,26 +742,26 @@ public static class PluginLoader Debug.LogMessage(LogEventLevel.Information, "Error Loading assembly {0}: {1}", loadedAssembly.Name, e.Message); Debug.LogMessage(LogEventLevel.Verbose, "{0}", e.StackTrace); - + // Add to incompatible plugins if this is likely a .NET 8 compatibility issue - if (e.Message.Contains("Could not load type") || + if (e.Message.Contains("Could not load type") || e.Message.Contains("Unable to load one or more of the requested types")) { - IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name, - $"Assembly loading error: {e.Message}", + IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name, + $"Assembly loading error: {e.Message}", null)); } - + continue; } } - + // Update incompatible plugins with dependency information var pluginDependencies = new Dictionary>(); // Populate pluginDependencies with relevant data // Example: pluginDependencies["PluginA"] = new List { "Dependency1", "Dependency2" }; UpdateIncompatiblePluginDependencies(pluginDependencies); - + // plugin dll will be loaded. Any classes in plugin should have a static constructor // that registers that class with the Core.DeviceFactory Debug.LogMessage(LogEventLevel.Information, "Done Loading Custom Plugin Types."); @@ -800,15 +783,15 @@ public static class PluginLoader // If it already has a requestedBy, skip it if (incompatiblePlugin.TriggeredBy != "Direct load") continue; - + // Find plugins that depend on this incompatible plugin foreach (var plugin in pluginDependencies) { string pluginName = plugin.Key; List dependencies = plugin.Value; - + // If this plugin depends on the incompatible plugin - if (dependencies.Contains(incompatiblePlugin.Name) || + if (dependencies.Contains(incompatiblePlugin.Name) || dependencies.Any(d => d.StartsWith(incompatiblePlugin.Name + ","))) { incompatiblePlugin.UpdateTriggeringPlugin(pluginName); @@ -853,7 +836,7 @@ public static class PluginLoader LoadDeviceFactories(deviceFactory); - if (!EssentialsPluginAssemblies.Contains(loadedAssembly)) + if(!EssentialsPluginAssemblies.Contains(loadedAssembly)) EssentialsPluginAssemblies.Add(loadedAssembly); } @@ -908,7 +891,7 @@ public static class PluginLoader // Load the types from any custom plugin assemblies LoadCustomPluginTypes(); } - + // Report on incompatible plugins if (IncompatiblePlugins.Count > 0) { @@ -917,12 +900,12 @@ public static class PluginLoader { if (plugin.TriggeredBy != "Direct load") { - Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1} (Required by: {2})", + Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1} (Required by: {2})", plugin.Name, plugin.Reason, plugin.TriggeredBy); } else { - Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1}", + Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1}", plugin.Name, plugin.Reason); } }