From 1b066a572487e21be6d62d1ffebb5064abc9c99c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 21 Mar 2025 16:49:22 +0800 Subject: [PATCH] Improve code quality --- Flow.Launcher/App.xaml.cs | 3 +- Flow.Launcher/Helper/SingleInstance.cs | 260 ++++++++++++------------- 2 files changed, 122 insertions(+), 141 deletions(-) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 23c77618f..19e932ea8 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -28,7 +28,6 @@ namespace Flow.Launcher public partial class App : IDisposable, ISingleInstanceApp { public static IPublicAPI API { get; private set; } - private const string Unique = "Flow.Launcher_Unique_Application_Mutex"; private static bool _disposed; private readonly Settings _settings; @@ -99,7 +98,7 @@ namespace Flow.Launcher [STAThread] public static void Main() { - if (SingleInstance.InitializeAsFirstInstance(Unique)) + if (SingleInstance.InitializeAsFirstInstance()) { using var application = new App(); application.InitializeComponent(); diff --git a/Flow.Launcher/Helper/SingleInstance.cs b/Flow.Launcher/Helper/SingleInstance.cs index e0e3075f6..76c109a39 100644 --- a/Flow.Launcher/Helper/SingleInstance.cs +++ b/Flow.Launcher/Helper/SingleInstance.cs @@ -6,155 +6,137 @@ using System.Windows; // http://blogs.microsoft.co.il/arik/2010/05/28/wpf-single-instance-application/ // modified to allow single instace restart -namespace Flow.Launcher.Helper +namespace Flow.Launcher.Helper; + +public interface ISingleInstanceApp { - public interface ISingleInstanceApp - { - void OnSecondAppStarted(); - } + void OnSecondAppStarted(); +} + +/// +/// This class checks to make sure that only one instance of +/// this application is running at a time. +/// +/// +/// Note: this class should be used with some caution, because it does no +/// security checking. For example, if one instance of an app that uses this class +/// is running as Administrator, any other instance, even if it is not +/// running as Administrator, can activate it with command line arguments. +/// For most apps, this will not be much of an issue. +/// +public static class SingleInstance where TApplication: Application, ISingleInstanceApp +{ + #region Private Fields /// - /// This class checks to make sure that only one instance of - /// this application is running at a time. + /// String delimiter used in channel names. /// - /// - /// Note: this class should be used with some caution, because it does no - /// security checking. For example, if one instance of an app that uses this class - /// is running as Administrator, any other instance, even if it is not - /// running as Administrator, can activate it with command line arguments. - /// For most apps, this will not be much of an issue. - /// - public static class SingleInstance - where TApplication: Application , ISingleInstanceApp - + private const string Delimiter = ":"; + + /// + /// Suffix to the channel name. + /// + private const string ChannelNameSuffix = "SingeInstanceIPCChannel"; + private const string InstanceMutexName = "Flow.Launcher_Unique_Application_Mutex"; + + /// + /// Application mutex. + /// + internal static Mutex SingleInstanceMutex { get; set; } + + #endregion + + #region Public Methods + + /// + /// Checks if the instance of the application attempting to start is the first instance. + /// If not, activates the first instance. + /// + /// True if this is the first instance of the application. + public static bool InitializeAsFirstInstance() { - #region Private Fields + // Build unique application Id and the IPC channel name. + string applicationIdentifier = InstanceMutexName + Environment.UserName; - /// - /// String delimiter used in channel names. - /// - private const string Delimiter = ":"; + string channelName = string.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix); - /// - /// Suffix to the channel name. - /// - private const string ChannelNameSuffix = "SingeInstanceIPCChannel"; - - /// - /// Application mutex. - /// - internal static Mutex singleInstanceMutex; - - #endregion - - #region Public Methods - - /// - /// Checks if the instance of the application attempting to start is the first instance. - /// If not, activates the first instance. - /// - /// True if this is the first instance of the application. - public static bool InitializeAsFirstInstance( string uniqueName ) + // Create mutex based on unique application Id to check if this is the first instance of the application. + SingleInstanceMutex = new Mutex(true, applicationIdentifier, out var firstInstance); + if (firstInstance) { - // Build unique application Id and the IPC channel name. - string applicationIdentifier = uniqueName + Environment.UserName; - - string channelName = String.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix); - - // Create mutex based on unique application Id to check if this is the first instance of the application. - bool firstInstance; - singleInstanceMutex = new Mutex(true, applicationIdentifier, out firstInstance); - if (firstInstance) - { - _ = CreateRemoteService(channelName); - return true; - } - else - { - _ = SignalFirstInstance(channelName); - return false; - } + _ = CreateRemoteServiceAsync(channelName); + return true; } - - /// - /// Cleans up single-instance code, clearing shared resources, mutexes, etc. - /// - public static void Cleanup() + else { - singleInstanceMutex?.ReleaseMutex(); + _ = SignalFirstInstanceAsync(channelName); + return false; } - - #endregion - - #region Private Methods - - /// - /// Creates a remote server pipe for communication. - /// Once receives signal from client, will activate first instance. - /// - /// Application's IPC channel name. - private static async Task CreateRemoteService(string channelName) - { - using (NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In)) - { - while(true) - { - // Wait for connection to the pipe - await pipeServer.WaitForConnectionAsync(); - if (Application.Current != null) - { - // Do an asynchronous call to ActivateFirstInstance function - Application.Current.Dispatcher.Invoke(ActivateFirstInstance); - } - // Disconect client - pipeServer.Disconnect(); - } - } - } - - /// - /// Creates a client pipe and sends a signal to server to launch first instance - /// - /// Application's IPC channel name. - /// - /// Command line arguments for the second instance, passed to the first instance to take appropriate action. - /// - private static async Task SignalFirstInstance(string channelName) - { - // Create a client pipe connected to server - using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out)) - { - // Connect to the available pipe - await pipeClient.ConnectAsync(0); - } - } - - /// - /// Callback for activating first instance of the application. - /// - /// Callback argument. - /// Always null. - private static object ActivateFirstInstanceCallback(object o) - { - ActivateFirstInstance(); - return null; - } - - /// - /// Activates the first instance of the application with arguments from a second instance. - /// - /// List of arguments to supply the first instance of the application. - private static void ActivateFirstInstance() - { - // Set main window state and process command line args - if (Application.Current == null) - { - return; - } - - ((TApplication)Application.Current).OnSecondAppStarted(); - } - - #endregion } + + /// + /// Cleans up single-instance code, clearing shared resources, mutexes, etc. + /// + public static void Cleanup() + { + SingleInstanceMutex?.ReleaseMutex(); + } + + #endregion + + #region Private Methods + + /// + /// Creates a remote server pipe for communication. + /// Once receives signal from client, will activate first instance. + /// + /// Application's IPC channel name. + private static async Task CreateRemoteServiceAsync(string channelName) + { + using NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In); + while (true) + { + // Wait for connection to the pipe + await pipeServer.WaitForConnectionAsync(); + + // Do an asynchronous call to ActivateFirstInstance function + Application.Current?.Dispatcher.Invoke(ActivateFirstInstance); + + // Disconect client + pipeServer.Disconnect(); + } + } + + /// + /// Creates a client pipe and sends a signal to server to launch first instance + /// + /// Application's IPC channel name. + /// + /// Command line arguments for the second instance, passed to the first instance to take appropriate action. + /// + private static async Task SignalFirstInstanceAsync(string channelName) + { + // Create a client pipe connected to server + using NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out); + + // Connect to the available pipe + await pipeClient.ConnectAsync(0); + } + + /// + /// Activates the first instance of the application with arguments from a second instance. + /// + /// List of arguments to supply the first instance of the application. + private static void ActivateFirstInstance() + { + // Set main window state and process command line args + if (Application.Current == null) + { + return; + } + + ((TApplication)Application.Current).OnSecondAppStarted(); + } + + #endregion }