diff --git a/mRemoteNG/App/Startup.cs b/mRemoteNG/App/Startup.cs index 5ac62825..f00b85a6 100644 --- a/mRemoteNG/App/Startup.cs +++ b/mRemoteNG/App/Startup.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Runtime.Versioning; @@ -8,6 +7,7 @@ using mRemoteNG.App.Info; using mRemoteNG.App.Initialization; using mRemoteNG.App.Update; using mRemoteNG.Config.Connections.Multiuser; +using mRemoteNG.Config.Settings.Registry; using mRemoteNG.Connection; using mRemoteNG.Messages; using mRemoteNG.Properties; @@ -22,6 +22,7 @@ namespace mRemoteNG.App [SupportedOSPlatform("windows")] public class Startup { + private RegistryLoader _RegistryLoader; private AppUpdater _appUpdate; private readonly ConnectionIconLoader _connectionIconLoader; private readonly FrmMain _frmMain = FrmMain.Default; @@ -30,7 +31,8 @@ namespace mRemoteNG.App private Startup() { - _appUpdate = new AppUpdater(); + _RegistryLoader = RegistryLoader.Instance; //created instance + _appUpdate = new AppUpdater(); _connectionIconLoader = new ConnectionIconLoader(GeneralAppInfo.HomePath + "\\Icons\\"); } diff --git a/mRemoteNG/Config/Settings/Registry/RegistryLoader.cs b/mRemoteNG/Config/Settings/Registry/RegistryLoader.cs new file mode 100644 index 00000000..90d0688f --- /dev/null +++ b/mRemoteNG/Config/Settings/Registry/RegistryLoader.cs @@ -0,0 +1,187 @@ +using mRemoteNG.App; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.Versioning; +using System.Threading.Tasks; + +namespace mRemoteNG.Config.Settings.Registry +{ + [SupportedOSPlatform("windows")] + public class RegistryLoader : IDisposable + { + /// + /// This instance is used to load registry settings at program startup.cs, ensuring that + /// these settings are applied to regular configurations and can override them as needed. + /// Once the settings are loaded, this lazy instance will primarily be used for configuring + /// the options page. After this initialization phase, the lazy instance may no longer be necessary and get disposed. + /// + private static readonly Lazy instance = + new(() => new RegistryLoader()); + + /// + /// Singleton instance of the RegistryMultiLoader class. + /// + public static RegistryLoader Instance => instance.Value; + + /// + /// Dictionary to store settings for all registry pages. + /// + public static ConcurrentDictionary RegistrySettings { get; private set; } + + /// + /// Static constructor that initializes the registry settings dictionary + /// and starts loading settings asynchronously at startup. + /// + static RegistryLoader() + { + RegistrySettings = new ConcurrentDictionary(); + // Start loading asynchronously on startup + _ = LoadAllAsync(); + } + + /// + /// Cleans up the registry settings by removing entries of a specified type. + /// + /// The type of entries to remove from the dictionary. + /// + /// If the dictionary becomes empty after removal and the singleton instance has been created, + /// it disposes of the instance to free resources. + /// + public static void Cleanup(Type deleteEntries) + { + // Remove the registry setting from the dictionary + RegistrySettings?.TryRemove(deleteEntries, out _); + + if (RegistrySettings.IsEmpty && instance.IsValueCreated) + { + Instance.Dispose(); + } + } + + /// + /// Asynchronously loads and applies multiple registry classes in parallel. + /// + /// A task representing the asynchronous operation. + /// + /// This method creates a list of tasks, each responsible for initializing + /// and applying a specific registry class. It awaits the completion of all tasks + /// using Task.WhenAll. + /// + private static async Task LoadAllAsync() + { +#if DEBUG + Stopwatch stopwatch = Stopwatch.StartNew(); +#endif + + try + { + // Create a list of tasks to initialize and apply each registry class asynchronously + var tasks = new List + { + LoadAndApplyAsync(), + LoadAndApplyAsync(), + LoadAndApplyAsync(), + LoadAndApplyAsync(), + LoadAndApplyAsync(), + LoadAndApplyAsync(), + LoadAndApplyAsync(), + LoadAndApplyAsync(), + LoadAndApplyAsync() + }; + + // Await all tasks to complete + await Task.WhenAll(tasks); + + Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, + $"Registry settings loaded and applied asynchronously in {typeof(RegistryLoader).Name}", true); + } + catch (Exception ex) + { + Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, + $"Registry settings error during load: {ex.Message}", true); + } + +#if DEBUG + stopwatch.Stop(); + Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, + $"Registry settings total async load time: {stopwatch.ElapsedMilliseconds} ms", true); +#endif + } + + /// + /// Asynchronously initializes and applies registry settings for a specified type. + /// + /// The type of the registry settings class, which must have a parameterless constructor. + /// A task representing the asynchronous operation. + /// + /// This method creates an instance of the specified type T, stores it in a dictionary + /// using its type as the key, and completes the task. + /// + private static async Task LoadAndApplyAsync() where T : new() + { + try + { + // create an instance of setting + var instance = new T(); + + // Store the instance in the dictionary using its type as key + RegistrySettings[typeof(T)] = instance; + + // complete task + await Task.CompletedTask; + } + catch (Exception ex) + { + Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, + $"Registry settings error during load {typeof(T).Name}: {ex.Message}", true); + } + } + + #region IDisposable Support + + /// + /// Implements the IDisposable pattern for resource cleanup. + /// + private bool disposedValue = false; // To detect redundant calls + + /// + /// Releases the resources used by the object. + /// + public void Dispose() + { + if (disposedValue) + return; + + Dispose(true); + GC.SuppressFinalize(this); + + disposedValue = true; + + Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, + $"Registry settings lazy instance of {typeof(RegistryLoader).Name} has been disposed.", true); + } + + /// + /// Performs the actual resource cleanup. + /// + /// True if managed resources should be released. + + protected virtual void Dispose(bool disposing) + { + if (disposing) + RegistrySettings?.Clear(); + } + + /// + /// Finalizer that is called to free unmanaged resources. + /// + ~RegistryLoader() + { + Dispose(false); + } + + #endregion IDisposable Support + } +} diff --git a/mRemoteNG/Tools/WindowsRegistry/WinRegistryEntry.cs b/mRemoteNG/Tools/WindowsRegistry/WinRegistryEntry.cs index 6e764943..ad3985b4 100644 --- a/mRemoteNG/Tools/WindowsRegistry/WinRegistryEntry.cs +++ b/mRemoteNG/Tools/WindowsRegistry/WinRegistryEntry.cs @@ -300,6 +300,9 @@ namespace mRemoteNG.Tools.WindowsRegistry AllowedValues = allowedValues; } + if (ElementType == typeof(string) && privateValue != null) + privateValue = EnforceStringInputValidity(privateValue); + return this; }