From e31df338d6e9d4e9aa58723140707ee2fcb4edf2 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Mon, 7 Jul 2025 10:06:59 -0500 Subject: [PATCH] chore: remove duplication namespace declaration Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Plugins/PluginLoader.cs | 1435 ++++++++--------- 1 file changed, 717 insertions(+), 718 deletions(-) diff --git a/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs b/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs index 2f0839da..9211080c 100644 --- a/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs +++ b/src/PepperDash.Essentials.Core/Plugins/PluginLoader.cs @@ -1,193 +1,192 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Crestron.SimplSharp; -using System.Reflection; -using System.IO; -using System.Reflection.PortableExecutable; -using System.Reflection.Metadata; - -using PepperDash.Core; -using PepperDash.Essentials.Core; -using Serilog.Events; - - -namespace PepperDash.Essentials; - +using System; +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharp; +using System.Reflection; +using System.IO; +using System.Reflection.PortableExecutable; +using System.Reflection.Metadata; + +using PepperDash.Core; +using PepperDash.Essentials.Core; +using Serilog.Events; + + +namespace PepperDash.Essentials; /// /// Provides functionality for loading and managing plugins and assemblies in the application. -/// -/// The class is responsible for discovering, loading, and managing assemblies -/// and plugins, including handling compatibility checks for .NET 8. It supports loading assemblies from the program -/// directory, plugins folder, and .cplz archives. Additionally, it tracks incompatible plugins and provides reporting -/// capabilities for loaded assemblies and their versions. -public static class PluginLoader -{ +/// +/// The class is responsible for discovering, loading, and managing assemblies +/// and plugins, including handling compatibility checks for .NET 8. It supports loading assemblies from the program +/// directory, plugins folder, and .cplz archives. Additionally, it tracks incompatible plugins and provides reporting +/// capabilities for loaded assemblies and their versions. +public static class PluginLoader +{ /// /// Gets the list of assemblies that have been loaded into the application. - /// - public static List LoadedAssemblies { get; private set; } - + /// + public static List LoadedAssemblies { get; private set; } + /// /// Represents a collection of assemblies loaded from the plugin folder. - /// - /// This field is used to store assemblies that have been dynamically loaded from a designated - /// plugin folder. It is intended for internal use and should not be modified directly. - private static readonly List LoadedPluginFolderAssemblies; - + /// + /// This field is used to store assemblies that have been dynamically loaded from a designated + /// plugin folder. It is intended for internal use and should not be modified directly. + private static readonly List LoadedPluginFolderAssemblies; + /// /// Gets the list of plugins that are incompatible with the current system or configuration. - /// - /// This property provides information about plugins that are not supported or cannot function - /// correctly in the current environment. Use this list to identify and handle incompatible plugins appropriately in - /// your application logic. - public static List IncompatiblePlugins { get; private set; } - + /// + /// This property provides information about plugins that are not supported or cannot function + /// correctly in the current environment. Use this list to identify and handle incompatible plugins appropriately in + /// your application logic. + public static List IncompatiblePlugins { get; private set; } + /// /// Gets the loaded assembly that contains the core functionality of the application. - /// - public static LoadedAssembly EssentialsAssembly { get; private set; } - + /// + public static LoadedAssembly EssentialsAssembly { get; private set; } + /// /// Gets the loaded assembly information for the PepperDash Core library. - /// - public static LoadedAssembly PepperDashCoreAssembly { get; private set; } - + /// + public static LoadedAssembly PepperDashCoreAssembly { get; private set; } + /// /// Gets the list of assemblies that are Essentials plugins loaded by the application. - /// - public static List EssentialsPluginAssemblies { get; private set; } - + /// + public static List EssentialsPluginAssemblies { get; private set; } + /// /// Gets the directory path where plugins are stored. - /// - private static string PluginDirectory => Global.FilePathPrefix + "plugins"; - + /// + private static string PluginDirectory => Global.FilePathPrefix + "plugins"; + /// /// Gets the directory path where loaded plugin assemblies are stored. - /// - private static string LoadedPluginsDirectoryPath => PluginDirectory + Global.DirectorySeparator + "loadedAssemblies"; - + /// + private static string LoadedPluginsDirectoryPath => PluginDirectory + Global.DirectorySeparator + "loadedAssemblies"; + /// /// Gets the path to the temporary directory used by the plugin. - /// - private static string TempDirectory => PluginDirectory + Global.DirectorySeparator + "temp"; - + /// + private 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. - /// - /// This collection contains the names of types that are deprecated, obsolete, or otherwise - /// incompatible with the intended usage of the application. These types may represent security risks, unsupported - /// features, or legacy APIs that should be avoided. + /// + /// This collection contains the names of types that are deprecated, obsolete, or otherwise + /// 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.Security.Cryptography.SHA1CryptoServiceProvider", - "System.Web.HttpUtility", - "System.Configuration.ConfigurationManager", - "System.Web.Services.Protocols.SoapHttpClientProtocol", - "System.Runtime.Remoting", - "System.EnterpriseServices", - "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter", - "System.Security.SecurityManager", - "System.Security.Permissions.FileIOPermission", - "System.AppDomain.CreateDomain" - ]; - + [ + "System.Net.ICertificatePolicy", + "System.Security.Cryptography.SHA1CryptoServiceProvider", + "System.Web.HttpUtility", + "System.Configuration.ConfigurationManager", + "System.Web.Services.Protocols.SoapHttpClientProtocol", + "System.Runtime.Remoting", + "System.EnterpriseServices", + "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter", + "System.Security.SecurityManager", + "System.Security.Permissions.FileIOPermission", + "System.AppDomain.CreateDomain" + ]; + /// /// Initializes static members of the class. - /// - /// This static constructor initializes the collections used to manage plugin assemblies and - /// track incompatible plugins. - static PluginLoader() - { - LoadedAssemblies = []; - LoadedPluginFolderAssemblies = []; - EssentialsPluginAssemblies = []; - IncompatiblePlugins = []; - } - + /// + /// This static constructor initializes the collections used to manage plugin assemblies and + /// track incompatible plugins. + static PluginLoader() + { + LoadedAssemblies = []; + LoadedPluginFolderAssemblies = []; + EssentialsPluginAssemblies = []; + IncompatiblePlugins = []; + } + /// /// Loads and registers assemblies from the application's directory that match specific naming patterns. - /// - /// This method scans the application's directory for assemblies with filenames containing - /// "Essentials" or "PepperDash" and registers them in the collection. It also - /// assigns specific assemblies to predefined properties, such as and , based on their names. Debug messages are logged at various stages to provide - /// detailed information about the process, including the number of assemblies found and their versions. This - /// method is intended to be used during application initialization to ensure required assemblies are loaded and - /// tracked. - public static void AddProgramAssemblies() - { - Debug.LogMessage(LogEventLevel.Verbose, "Getting Assemblies loaded with Essentials"); - // Get the loaded assembly filenames - var appDi = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix); - var assemblyFiles = appDi.GetFiles("*.dll"); - - 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; - Assembly assembly = null; - - switch (fi.Name) - { - case ("PepperDashEssentials.dll"): - { - version = Global.AssemblyVersion; - EssentialsAssembly = new LoadedAssembly(fi.Name, version, assembly); - break; - } - case ("PepperDash_Essentials_Core.dll"): - { - version = Global.AssemblyVersion; - break; - } - case ("Essentials Devices Common.dll"): - { - version = Global.AssemblyVersion; - break; - } - case ("PepperDashCore.dll"): - { - Debug.LogMessage(LogEventLevel.Verbose, "Found PepperDash_Core.dll"); - version = Debug.PepperDashCoreVersion; - Debug.LogMessage(LogEventLevel.Verbose, "PepperDash_Core Version: {0}", version); - PepperDashCoreAssembly = new LoadedAssembly(fi.Name, version, assembly); - break; - } - } - - LoadedAssemblies.Add(new LoadedAssembly(fi.Name, version, assembly)); - } - - if (Debug.Level > 1) - { - Debug.LogMessage(LogEventLevel.Verbose, "Loaded Assemblies:"); - - foreach (var assembly in LoadedAssemblies) - { - Debug.LogMessage(LogEventLevel.Verbose, "Assembly: {0}", assembly.Name); - } - } - } - + /// + /// This method scans the application's directory for assemblies with filenames containing + /// "Essentials" or "PepperDash" and registers them in the collection. It also + /// assigns specific assemblies to predefined properties, such as and , based on their names. Debug messages are logged at various stages to provide + /// detailed information about the process, including the number of assemblies found and their versions. This + /// method is intended to be used during application initialization to ensure required assemblies are loaded and + /// tracked. + public static void AddProgramAssemblies() + { + Debug.LogMessage(LogEventLevel.Verbose, "Getting Assemblies loaded with Essentials"); + // Get the loaded assembly filenames + var appDi = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix); + var assemblyFiles = appDi.GetFiles("*.dll"); + + 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; + Assembly assembly = null; + + switch (fi.Name) + { + case ("PepperDashEssentials.dll"): + { + version = Global.AssemblyVersion; + EssentialsAssembly = new LoadedAssembly(fi.Name, version, assembly); + break; + } + case ("PepperDash_Essentials_Core.dll"): + { + version = Global.AssemblyVersion; + break; + } + case ("Essentials Devices Common.dll"): + { + version = Global.AssemblyVersion; + break; + } + case ("PepperDashCore.dll"): + { + Debug.LogMessage(LogEventLevel.Verbose, "Found PepperDash_Core.dll"); + version = Debug.PepperDashCoreVersion; + Debug.LogMessage(LogEventLevel.Verbose, "PepperDash_Core Version: {0}", version); + PepperDashCoreAssembly = new LoadedAssembly(fi.Name, version, assembly); + break; + } + } + + LoadedAssemblies.Add(new LoadedAssembly(fi.Name, version, assembly)); + } + + if (Debug.Level > 1) + { + Debug.LogMessage(LogEventLevel.Verbose, "Loaded Assemblies:"); + + foreach (var assembly in LoadedAssemblies) + { + Debug.LogMessage(LogEventLevel.Verbose, "Assembly: {0}", assembly.Name); + } + } + } + /// /// Associates the specified assembly with the given name in the loaded assemblies collection. /// /// If an assembly with the specified name already exists in the loaded assemblies collection, /// this method updates its associated assembly. If no matching name is found, the method does nothing. /// The name used to identify the assembly. This value is case-sensitive and must not be null or empty. - /// The assembly to associate with the specified name. This value must not be null. - public static void AddLoadedAssembly(string name, Assembly assembly) - { - var loadedAssembly = LoadedAssemblies.FirstOrDefault(la => la.Name.Equals(name)); - - loadedAssembly?.SetAssembly(assembly); - } - + /// The assembly to associate with the specified name. This value must not be null. + public static void AddLoadedAssembly(string name, Assembly assembly) + { + var loadedAssembly = LoadedAssemblies.FirstOrDefault(la => la.Name.Equals(name)); + + loadedAssembly?.SetAssembly(assembly); + } + /// /// Determines whether a plugin assembly is compatible with .NET 8. /// @@ -195,19 +194,19 @@ public static class PluginLoader /// for known incompatible types, inspecting custom attributes, and collecting assembly references. If the analysis /// encounters an error, the method returns with an appropriate error message. /// The file path to the plugin assembly to analyze. - /// A tuple containing the following: if the plugin - /// is compatible with .NET 8; otherwise, . A - /// string providing the reason for incompatibility, or if the plugin is - /// compatible. A list of assembly references found in the - /// plugin. - public static (bool IsCompatible, string Reason, List References) IsPluginCompatibleWithNet8(string filePath) - { - try - { + /// A tuple containing the following: if the plugin + /// is compatible with .NET 8; otherwise, . A + /// string providing the reason for incompatibility, or if the plugin is + /// compatible. A list of assembly references found in the + /// plugin. + public static (bool IsCompatible, string Reason, List References) IsPluginCompatibleWithNet8(string filePath) + { + try + { List referencedAssemblies = []; using FileStream fs = new(filePath, FileMode.Open, - FileAccess.Read, FileShare.ReadWrite); + FileAccess.Read, FileShare.ReadWrite); using PEReader peReader = new(fs); if (!peReader.HasMetadata) @@ -264,14 +263,14 @@ public static class PluginLoader } // If we can't determine incompatibility, assume it's compatible - return (true, null, referencedAssemblies); - } - catch (Exception ex) - { - return (false, $"Error analyzing assembly: {ex.Message}", new List()); - } - } - + return (true, null, referencedAssemblies); + } + catch (Exception ex) + { + return (false, $"Error analyzing assembly: {ex.Message}", new List()); + } + } + /// /// Loads an assembly from the specified file path and verifies its compatibility with .NET 8. /// @@ -285,80 +284,80 @@ public static class PluginLoader /// compatibility issues. /// The full path to the assembly file to load. This cannot be null or empty. /// An optional identifier for the entity requesting the load operation. This can be null. - /// A object representing the loaded assembly if the operation succeeds; otherwise, - /// . - private static LoadedAssembly LoadAssembly(string filePath, string requestedBy = null) - { - try - { - // Check .NET 8 compatibility before loading - var (isCompatible, reason, references) = IsPluginCompatibleWithNet8(filePath); - if (!isCompatible) - { - 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; - } - - 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.LogMessage(LogEventLevel.Information, "Loaded assembly '{0}', version {1}", loadedAssembly.Name, loadedAssembly.Version); - return loadedAssembly; - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Unable to load assembly: '{0}'", filePath); - } - return null; - } - 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); - var assyVersion = GetAssemblyVersion(existingAssembly); - var loadedAssembly = new LoadedAssembly(existingAssembly.GetName().Name, assyVersion, existingAssembly); - 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) - { - string fileName = Path.GetFileName(filePath); - - // Check if this might be a .NET Framework compatibility issue - 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}", - fileName, ex.Message); - IncompatiblePlugins.Add(new IncompatiblePlugin(fileName, ex.Message, requestedBy)); - } - else - { - Debug.LogMessage(ex, "Error loading assembly from {path}", null, filePath); - } - return null; - } - } - + /// A object representing the loaded assembly if the operation succeeds; otherwise, + /// . + private static LoadedAssembly LoadAssembly(string filePath, string requestedBy = null) + { + try + { + // Check .NET 8 compatibility before loading + var (isCompatible, reason, references) = IsPluginCompatibleWithNet8(filePath); + if (!isCompatible) + { + 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; + } + + 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.LogMessage(LogEventLevel.Information, "Loaded assembly '{0}', version {1}", loadedAssembly.Name, loadedAssembly.Version); + return loadedAssembly; + } + else + { + Debug.LogMessage(LogEventLevel.Information, "Unable to load assembly: '{0}'", filePath); + } + return null; + } + 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); + var assyVersion = GetAssemblyVersion(existingAssembly); + var loadedAssembly = new LoadedAssembly(existingAssembly.GetName().Name, assyVersion, existingAssembly); + 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) + { + string fileName = Path.GetFileName(filePath); + + // Check if this might be a .NET Framework compatibility issue + 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}", + fileName, ex.Message); + IncompatiblePlugins.Add(new IncompatiblePlugin(fileName, ex.Message, requestedBy)); + } + else + { + Debug.LogMessage(ex, "Error loading assembly from {path}", null, filePath); + } + return null; + } + } + /// /// Retrieves the version information of the specified assembly. /// @@ -366,52 +365,52 @@ public static class PluginLoader /// cref="AssemblyInformationalVersionAttribute"/>. If the attribute is not present, it falls back to the assembly's /// version as defined in its metadata. /// The assembly from which to retrieve the version information. Cannot be . - /// A string representing the version of the assembly. If the assembly has an , its value is returned. Otherwise, the assembly's - /// version is returned in the format "Major.Minor.Build.Revision". - public 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; - } - } - + /// A string representing the version of the assembly. If the assembly has an , its value is returned. Otherwise, the assembly's + /// version is returned in the format "Major.Minor.Build.Revision". + public 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; + } + } + /// /// Determines whether an assembly with the specified name is currently loaded. /// /// This method performs a case-sensitive comparison to determine if the specified assembly is /// loaded. It logs verbose messages indicating the status of the check. /// The name of the assembly to check. This value is case-sensitive. - /// if an assembly with the specified name is loaded; otherwise, . - public static bool CheckIfAssemblyLoaded(string name) - { - Debug.LogMessage(LogEventLevel.Verbose, "Checking if assembly: {0} is loaded...", name); - var loadedAssembly = LoadedAssemblies.FirstOrDefault(s => s.Name.Equals(name)); - - if (loadedAssembly != null) - { - Debug.LogMessage(LogEventLevel.Verbose, "Assembly already loaded."); - return true; - } - else - { - Debug.LogMessage(LogEventLevel.Verbose, "Assembly not loaded."); - return false; - } - } - + /// if an assembly with the specified name is loaded; otherwise, . + public static bool CheckIfAssemblyLoaded(string name) + { + Debug.LogMessage(LogEventLevel.Verbose, "Checking if assembly: {0} is loaded...", name); + var loadedAssembly = LoadedAssemblies.FirstOrDefault(s => s.Name.Equals(name)); + + if (loadedAssembly != null) + { + Debug.LogMessage(LogEventLevel.Verbose, "Assembly already loaded."); + return true; + } + else + { + Debug.LogMessage(LogEventLevel.Verbose, "Assembly not loaded."); + return false; + } + } + /// /// Reports the versions of the Essentials framework, PepperDash Core, and loaded plugins to the console. /// @@ -419,304 +418,304 @@ public static class PluginLoader /// all loaded Essentials plugins to the Crestron console. If any incompatible plugins are detected, their details /// are also reported, including the reason for incompatibility and the plugin that required them, if /// applicable. - /// The command string that triggered the version report. This parameter is not used directly by the method. - public static void ReportAssemblyVersions(string command) - { - CrestronConsole.ConsoleCommandResponse("Essentials Version: {0}" + CrestronEnvironment.NewLine, Global.AssemblyVersion); - CrestronConsole.ConsoleCommandResponse("PepperDash Core Version: {0}" + CrestronEnvironment.NewLine, PepperDashCoreAssembly.Version); - CrestronConsole.ConsoleCommandResponse("Essentials Plugin Versions:" + CrestronEnvironment.NewLine); - foreach (var assembly in EssentialsPluginAssemblies) - { - CrestronConsole.ConsoleCommandResponse("{0} Version: {1}" + CrestronEnvironment.NewLine, assembly.Name, assembly.Version); - } - - if (IncompatiblePlugins.Count > 0) - { - CrestronConsole.ConsoleCommandResponse("Incompatible Plugins:" + CrestronEnvironment.NewLine); - foreach (var plugin in IncompatiblePlugins) - { - if (plugin.TriggeredBy != "Direct load") - { - CrestronConsole.ConsoleCommandResponse("{0}: {1} (Required by: {2})" + CrestronEnvironment.NewLine, - plugin.Name, plugin.Reason, plugin.TriggeredBy); - } - else - { - CrestronConsole.ConsoleCommandResponse("{0}: {1}" + CrestronEnvironment.NewLine, - plugin.Name, plugin.Reason); - } - } - } + /// The command string that triggered the version report. This parameter is not used directly by the method. + public static void ReportAssemblyVersions(string command) + { + CrestronConsole.ConsoleCommandResponse("Essentials Version: {0}" + CrestronEnvironment.NewLine, Global.AssemblyVersion); + CrestronConsole.ConsoleCommandResponse("PepperDash Core Version: {0}" + CrestronEnvironment.NewLine, PepperDashCoreAssembly.Version); + CrestronConsole.ConsoleCommandResponse("Essentials Plugin Versions:" + CrestronEnvironment.NewLine); + foreach (var assembly in EssentialsPluginAssemblies) + { + CrestronConsole.ConsoleCommandResponse("{0} Version: {1}" + CrestronEnvironment.NewLine, assembly.Name, assembly.Version); + } + + if (IncompatiblePlugins.Count > 0) + { + CrestronConsole.ConsoleCommandResponse("Incompatible Plugins:" + CrestronEnvironment.NewLine); + foreach (var plugin in IncompatiblePlugins) + { + if (plugin.TriggeredBy != "Direct load") + { + CrestronConsole.ConsoleCommandResponse("{0}: {1} (Required by: {2})" + CrestronEnvironment.NewLine, + plugin.Name, plugin.Reason, plugin.TriggeredBy); + } + else + { + CrestronConsole.ConsoleCommandResponse("{0}: {1}" + CrestronEnvironment.NewLine, + plugin.Name, plugin.Reason); + } + } + } } /// /// Moves .dll assemblies from the plugins folder to the loaded plugins directory. - /// - /// This method scans the plugins folder for .dll files and moves them to the loaded plugins - /// directory if they are not already loaded. If a file with the same name exists in the target directory, it is - /// replaced. The method logs the process at various stages and handles exceptions for individual files to ensure - /// the operation continues for other files. - private static void MoveDllAssemblies() - { - Debug.LogMessage(LogEventLevel.Information, "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.LogMessage(LogEventLevel.Information, "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.LogMessage(LogEventLevel.Information, "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.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", pluginFile.FullName, filePath); - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Skipping assembly: {0}. There is already an assembly with that name loaded.", pluginFile.FullName); - } - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Verbose, "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.LogMessage(LogEventLevel.Information, "Done with .dll assemblies"); - } - + /// + /// This method scans the plugins folder for .dll files and moves them to the loaded plugins + /// directory if they are not already loaded. If a file with the same name exists in the target directory, it is + /// replaced. The method logs the process at various stages and handles exceptions for individual files to ensure + /// the operation continues for other files. + private static void MoveDllAssemblies() + { + Debug.LogMessage(LogEventLevel.Information, "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.LogMessage(LogEventLevel.Information, "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.LogMessage(LogEventLevel.Information, "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.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", pluginFile.FullName, filePath); + } + else + { + Debug.LogMessage(LogEventLevel.Information, "Skipping assembly: {0}. There is already an assembly with that name loaded.", pluginFile.FullName); + } + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Verbose, "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.LogMessage(LogEventLevel.Information, "Done with .dll assemblies"); + } + /// /// Extracts and processes .cplz archive files found in the specified directories, moving their contents to the /// appropriate plugin directory. - /// - /// This method searches for .cplz files in the plugin directory and user folder, extracts their - /// contents, and moves any .dll files to the loaded plugins directory. If a .dll file with the same name already - /// exists in the target directory, it is replaced with the new file. Temporary files and directories created during - /// the process are cleaned up after the operation completes. - private static void UnzipAndMoveCplzArchives() - { - Debug.LogMessage(LogEventLevel.Information, "Looking for .cplz archives from plugins folder..."); - var di = new DirectoryInfo(PluginDirectory); - var zFiles = di.GetFiles("*.cplz"); - - //// Find cplz files at the root of the user folder. Makes development/testing easier for VC-4, and helps with mistakes by end users - - //var userDi = new DirectoryInfo(Global.FilePathPrefix); - //var userZFiles = userDi.GetFiles("*.cplz"); - - Debug.LogInformation("Checking {folder} for .cplz files", Global.FilePathPrefix); - var cplzFiles = Directory.GetFiles(Global.FilePathPrefix, "*.cplz", SearchOption.AllDirectories) - .Select(f => new FileInfo(f)) - .ToArray(); - - if (cplzFiles.Length > 0) - { - if (!Directory.Exists(LoadedPluginsDirectoryPath)) - { - Directory.CreateDirectory(LoadedPluginsDirectoryPath); - } - } - - foreach (var zfi in cplzFiles) - { - Directory.CreateDirectory(TempDirectory); - var tempDi = new DirectoryInfo(TempDirectory); - - Debug.LogMessage(LogEventLevel.Information, "Found cplz: {0}. Unzipping into temp plugins directory", zfi.FullName); - var result = CrestronZIP.Unzip(zfi.FullName, tempDi.FullName); - Debug.LogMessage(LogEventLevel.Information, "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.LogMessage(LogEventLevel.Information, "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.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", tempFile.FullName, filePath); - } - else - { - Debug.LogMessage(LogEventLevel.Information, "Skipping assembly: {0}. There is already an assembly with that name loaded.", tempFile.FullName); - } - } - catch (Exception e) - { - Debug.LogMessage(LogEventLevel.Verbose, "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.LogMessage(LogEventLevel.Information, "Done with .cplz archives"); - } - + /// + /// This method searches for .cplz files in the plugin directory and user folder, extracts their + /// contents, and moves any .dll files to the loaded plugins directory. If a .dll file with the same name already + /// exists in the target directory, it is replaced with the new file. Temporary files and directories created during + /// the process are cleaned up after the operation completes. + private static void UnzipAndMoveCplzArchives() + { + Debug.LogMessage(LogEventLevel.Information, "Looking for .cplz archives from plugins folder..."); + var di = new DirectoryInfo(PluginDirectory); + var zFiles = di.GetFiles("*.cplz"); + + //// Find cplz files at the root of the user folder. Makes development/testing easier for VC-4, and helps with mistakes by end users + + //var userDi = new DirectoryInfo(Global.FilePathPrefix); + //var userZFiles = userDi.GetFiles("*.cplz"); + + Debug.LogInformation("Checking {folder} for .cplz files", Global.FilePathPrefix); + var cplzFiles = Directory.GetFiles(Global.FilePathPrefix, "*.cplz", SearchOption.AllDirectories) + .Select(f => new FileInfo(f)) + .ToArray(); + + if (cplzFiles.Length > 0) + { + if (!Directory.Exists(LoadedPluginsDirectoryPath)) + { + Directory.CreateDirectory(LoadedPluginsDirectoryPath); + } + } + + foreach (var zfi in cplzFiles) + { + Directory.CreateDirectory(TempDirectory); + var tempDi = new DirectoryInfo(TempDirectory); + + Debug.LogMessage(LogEventLevel.Information, "Found cplz: {0}. Unzipping into temp plugins directory", zfi.FullName); + var result = CrestronZIP.Unzip(zfi.FullName, tempDi.FullName); + Debug.LogMessage(LogEventLevel.Information, "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.LogMessage(LogEventLevel.Information, "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.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", tempFile.FullName, filePath); + } + else + { + Debug.LogMessage(LogEventLevel.Information, "Skipping assembly: {0}. There is already an assembly with that name loaded.", tempFile.FullName); + } + } + catch (Exception e) + { + Debug.LogMessage(LogEventLevel.Verbose, "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.LogMessage(LogEventLevel.Information, "Done with .cplz archives"); + } + /// /// Loads plugin assemblies from the designated plugin directory, checks their compatibility with .NET 8, and loads /// the compatible assemblies into the application. - /// - /// This method scans the plugin directory for all `.dll` files, verifies their compatibility - /// with .NET 8, and attempts to load the compatible assemblies. Assemblies that are incompatible are logged as - /// warnings and added to a list of incompatible plugins for further inspection. - private static void LoadPluginAssemblies() - { - Debug.LogMessage(LogEventLevel.Information, "Loading assemblies from loadedPlugins folder..."); - var pluginDi = new DirectoryInfo(LoadedPluginsDirectoryPath); - var pluginFiles = pluginDi.GetFiles("*.dll"); - - Debug.LogMessage(LogEventLevel.Verbose, "Found {0} plugin assemblies to load", pluginFiles.Length); - - // 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) - { + /// + /// This method scans the plugin directory for all `.dll` files, verifies their compatibility + /// with .NET 8, and attempts to load the compatible assemblies. Assemblies that are incompatible are logged as + /// warnings and added to a list of incompatible plugins for further inspection. + private static void LoadPluginAssemblies() + { + Debug.LogMessage(LogEventLevel.Information, "Loading assemblies from loadedPlugins folder..."); + var pluginDi = new DirectoryInfo(LoadedPluginsDirectoryPath); + var pluginFiles = pluginDi.GetFiles("*.dll"); + + Debug.LogMessage(LogEventLevel.Verbose, "Found {0} plugin assemblies to load", pluginFiles.Length); + + // First, check compatibility of all assemblies before loading any + var assemblyCompatibility = new Dictionary References)>(); + + 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); - } - } - - Debug.LogMessage(LogEventLevel.Information, "All Plugins Loaded."); - } - + 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); + } + } + + Debug.LogMessage(LogEventLevel.Information, "All Plugins Loaded."); + } + /// /// Loads and initializes custom plugin types from the assemblies in the plugin folder. - /// - /// This method iterates through all loaded plugin assemblies, identifies types that implement - /// the interface, and attempts to instantiate and load them. Assemblies or - /// types that cannot be loaded due to missing dependencies, type loading errors, or other exceptions are logged, - /// and incompatible plugins are tracked for further analysis. - 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 - { - var assy = loadedAssembly.Assembly; - Type[] types = []; - try - { - types = assy.GetTypes(); - Debug.LogMessage(LogEventLevel.Debug, $"Got types for assembly {assy.GetName().Name}"); - } - catch (ReflectionTypeLoadException e) - { - 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) - { - if (loaderEx is FileNotFoundException fileNotFoundEx) - { - string missingAssembly = fileNotFoundEx.FileName; - if (!string.IsNullOrEmpty(missingAssembly)) - { - 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}", - loadedAssembly.Name)); - } - } - } - - Debug.LogMessage(LogEventLevel.Verbose, e.StackTrace); - continue; - } - catch (TypeLoadException e) - { - 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") || - e.Message.Contains("Unable to load one or more of the requested types")) - { - IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name, - $"Type loading error: {e.Message}", - null)); - } - - continue; - } - - foreach (var type in types) - { - try + /// + /// This method iterates through all loaded plugin assemblies, identifies types that implement + /// the interface, and attempts to instantiate and load them. Assemblies or + /// types that cannot be loaded due to missing dependencies, type loading errors, or other exceptions are logged, + /// and incompatible plugins are tracked for further analysis. + 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 + { + var assy = loadedAssembly.Assembly; + Type[] types = []; + try + { + types = assy.GetTypes(); + Debug.LogMessage(LogEventLevel.Debug, $"Got types for assembly {assy.GetName().Name}"); + } + catch (ReflectionTypeLoadException e) + { + 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) + { + if (loaderEx is FileNotFoundException fileNotFoundEx) + { + string missingAssembly = fileNotFoundEx.FileName; + if (!string.IsNullOrEmpty(missingAssembly)) + { + 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}", + loadedAssembly.Name)); + } + } + } + + Debug.LogMessage(LogEventLevel.Verbose, e.StackTrace); + continue; + } + catch (TypeLoadException e) + { + 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") || + e.Message.Contains("Unable to load one or more of the requested types")) + { + IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name, + $"Type loading error: {e.Message}", + null)); + } + + continue; + } + + foreach (var type in types) + { + try { if (!typeof(IPluginDeviceFactory).IsAssignableFrom(type) || type.IsAbstract) { @@ -726,82 +725,82 @@ public static class PluginLoader var plugin = (IPluginDeviceFactory)Activator.CreateInstance(type); LoadCustomPlugin(plugin, loadedAssembly); } - catch (NotSupportedException) - { - //this happens for dlls that aren't PD dlls, like ports of Mono classes into S#. Swallowing. - } - catch (Exception ex) - { - Debug.LogError("Load Plugin not found. {assemblyName}.{typeName} is not a plugin factory. Exception: {exception}", - loadedAssembly.Name, type.Name, ex.Message); - continue; - } - } - } - catch (Exception e) - { - 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") || - e.Message.Contains("Unable to load one or more of the requested types")) - { - 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."); - } - + catch (NotSupportedException) + { + //this happens for dlls that aren't PD dlls, like ports of Mono classes into S#. Swallowing. + } + catch (Exception ex) + { + Debug.LogError("Load Plugin not found. {assemblyName}.{typeName} is not a plugin factory. Exception: {exception}", + loadedAssembly.Name, type.Name, ex.Message); + continue; + } + } + } + catch (Exception e) + { + 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") || + e.Message.Contains("Unable to load one or more of the requested types")) + { + 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."); + } + /// /// Updates the triggering plugin information for incompatible plugins based on their dependencies. /// /// This method iterates through a predefined list of incompatible plugins and updates their /// triggering plugin information if they were directly loaded and are found to be dependencies of other plugins. /// The update is performed for the first plugin that depends on the incompatible plugin. - /// A dictionary where the key is the name of a plugin and the value is a list of its dependencies. Each dependency - /// is represented as a string, which may include additional metadata. - private static void UpdateIncompatiblePluginDependencies(Dictionary> pluginDependencies) - { - // For each incompatible plugin - foreach (var incompatiblePlugin in IncompatiblePlugins) - { - // 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) || - dependencies.Any(d => d.StartsWith(incompatiblePlugin.Name + ","))) - { - incompatiblePlugin.UpdateTriggeringPlugin(pluginName); - break; - } - } - } - } - + /// A dictionary where the key is the name of a plugin and the value is a list of its dependencies. Each dependency + /// is represented as a string, which may include additional metadata. + private static void UpdateIncompatiblePluginDependencies(Dictionary> pluginDependencies) + { + // For each incompatible plugin + foreach (var incompatiblePlugin in IncompatiblePlugins) + { + // 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) || + dependencies.Any(d => d.StartsWith(incompatiblePlugin.Name + ","))) + { + incompatiblePlugin.UpdateTriggeringPlugin(pluginName); + break; + } + } + } + } + /// /// Loads a custom plugin and performs a dependency check to ensure compatibility with the required Essentials /// framework version. @@ -812,33 +811,33 @@ public static class PluginLoader /// The plugin to be loaded, implementing the interface. If the plugin also /// implements , additional checks for development versions are /// performed. - /// The assembly associated with the plugin being loaded. This is used for logging and tracking purposes. - private static void LoadCustomPlugin(IPluginDeviceFactory deviceFactory, LoadedAssembly loadedAssembly) - { + /// The assembly associated with the plugin being loaded. This is used for logging and tracking purposes. + private static void LoadCustomPlugin(IPluginDeviceFactory deviceFactory, LoadedAssembly loadedAssembly) + { var developmentDeviceFactory = deviceFactory as IPluginDevelopmentDeviceFactory; var passed = developmentDeviceFactory != null ? Global.IsRunningDevelopmentVersion (developmentDeviceFactory.DevelopmentEssentialsFrameworkVersions, developmentDeviceFactory.MinimumEssentialsFrameworkVersion) - : Global.IsRunningMinimumVersionOrHigher(deviceFactory.MinimumEssentialsFrameworkVersion); - - if (!passed) - { - Debug.LogInformation( - "\r\n********************\r\n\tPlugin indicates minimum Essentials version {minimumEssentialsVersion}. Dependency check failed. Skipping Plugin {pluginName}\r\n********************", - deviceFactory.MinimumEssentialsFrameworkVersion, loadedAssembly.Name); - return; - } - else - { - Debug.LogInformation("Passed plugin passed dependency check (required version {essentialsMinimumVersion})", deviceFactory.MinimumEssentialsFrameworkVersion); - } - - Debug.LogInformation("Loading plugin: {pluginName}", loadedAssembly.Name); + : Global.IsRunningMinimumVersionOrHigher(deviceFactory.MinimumEssentialsFrameworkVersion); - LoadDeviceFactories(deviceFactory); - - if(!EssentialsPluginAssemblies.Contains(loadedAssembly)) - EssentialsPluginAssemblies.Add(loadedAssembly); + if (!passed) + { + Debug.LogInformation( + "\r\n********************\r\n\tPlugin indicates minimum Essentials version {minimumEssentialsVersion}. Dependency check failed. Skipping Plugin {pluginName}\r\n********************", + deviceFactory.MinimumEssentialsFrameworkVersion, loadedAssembly.Name); + return; + } + else + { + Debug.LogInformation("Passed plugin passed dependency check (required version {essentialsMinimumVersion})", deviceFactory.MinimumEssentialsFrameworkVersion); + } + + Debug.LogInformation("Loading plugin: {pluginName}", loadedAssembly.Name); + + LoadDeviceFactories(deviceFactory); + + if(!EssentialsPluginAssemblies.Contains(loadedAssembly)) + EssentialsPluginAssemblies.Add(loadedAssembly); } /// @@ -858,59 +857,59 @@ public static class PluginLoader var snippetAttribute = deviceFactory.FactoryType.GetCustomAttributes(typeof(ConfigSnippetAttribute), true) as ConfigSnippetAttribute[]; DeviceFactory.AddFactoryForType(typeName.ToLower(), description, deviceFactory.FactoryType, deviceFactory.BuildDevice); } - } + } /// /// Loads plugins from the designated plugin directory, processes them, and integrates them into the application. - /// - /// This method performs the following steps: Checks if - /// the plugin directory exists. Processes any plugin files, including .dll - /// and .cplz files, by moving or extracting them as needed. Loads - /// assemblies from the processed plugins into the application domain. - /// Identifies and reports any incompatible plugins, including the reason for - /// incompatibility. Plugins that are successfully loaded are made available for use, - /// while incompatible plugins are logged for review. - public static void LoadPlugins() - { - Debug.LogMessage(LogEventLevel.Information, "Attempting to Load Plugins from {_pluginDirectory}", PluginDirectory); - - if (Directory.Exists(PluginDirectory)) - { - Debug.LogMessage(LogEventLevel.Information, "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(); - } - - // Report on incompatible plugins - if (IncompatiblePlugins.Count > 0) - { - Debug.LogMessage(LogEventLevel.Warning, "Found {0} incompatible plugins:", IncompatiblePlugins.Count); - foreach (var plugin in IncompatiblePlugins) - { - if (plugin.TriggeredBy != "Direct load") - { - Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1} (Required by: {2})", - plugin.Name, plugin.Reason, plugin.TriggeredBy); - } - else - { - Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1}", - plugin.Name, plugin.Reason); - } - } - } - } - } -} + /// + /// This method performs the following steps: Checks if + /// the plugin directory exists. Processes any plugin files, including .dll + /// and .cplz files, by moving or extracting them as needed. Loads + /// assemblies from the processed plugins into the application domain. + /// Identifies and reports any incompatible plugins, including the reason for + /// incompatibility. Plugins that are successfully loaded are made available for use, + /// while incompatible plugins are logged for review. + public static void LoadPlugins() + { + Debug.LogMessage(LogEventLevel.Information, "Attempting to Load Plugins from {_pluginDirectory}", PluginDirectory); + + if (Directory.Exists(PluginDirectory)) + { + Debug.LogMessage(LogEventLevel.Information, "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(); + } + + // Report on incompatible plugins + if (IncompatiblePlugins.Count > 0) + { + Debug.LogMessage(LogEventLevel.Warning, "Found {0} incompatible plugins:", IncompatiblePlugins.Count); + foreach (var plugin in IncompatiblePlugins) + { + if (plugin.TriggeredBy != "Direct load") + { + Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1} (Required by: {2})", + plugin.Name, plugin.Reason, plugin.TriggeredBy); + } + else + { + Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1}", + plugin.Name, plugin.Reason); + } + } + } + } + } +}