using System.IO.Pipes; using System.IO; using System.Text; namespace XFEToolBox.Client.Utilities; internal sealed class SingleInstanceService : IDisposable { private const string MutexName = "Local\\XFEToolBox.Client.SingleInstance"; private const string PipeName = "XFEToolBox.Client.SingleInstance.Pipe"; private readonly Mutex mutex; private readonly string pipeName; private readonly CancellationTokenSource cancellationSource = new(); private Task? listenerTask; public SingleInstanceService() : this(string.Empty) { } internal SingleInstanceService(string scope) { var suffix = string.IsNullOrWhiteSpace(scope) ? string.Empty : $".{scope}"; pipeName = PipeName + suffix; mutex = new Mutex(initiallyOwned: true, MutexName + suffix, out var createdNew); IsFirstInstance = createdNew; } public bool IsFirstInstance { get; } public void StartListening(Action messageReceived) { if (!IsFirstInstance || listenerTask is not null) return; listenerTask = Task.Run(async () => { while (!cancellationSource.IsCancellationRequested) { try { await using var server = new NamedPipeServerStream( pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly); await server.WaitForConnectionAsync(cancellationSource.Token); using var reader = new StreamReader(server, Encoding.UTF8, false, 1024, leaveOpen: true); var message = await reader.ReadToEndAsync(cancellationSource.Token); if (!string.IsNullOrWhiteSpace(message)) messageReceived(message.Trim()); } catch (OperationCanceledException) { break; } catch { await Task.Delay(250, cancellationSource.Token).ConfigureAwait(false); } } }, cancellationSource.Token); } public static Task SendAsync(string message) => SendToPipeAsync(message, PipeName); internal static async Task SendAsync(string message, string scope) => await SendToPipeAsync(message, PipeName + $".{scope}"); private static async Task SendToPipeAsync(string message, string pipeName) { try { await using var client = new NamedPipeClientStream( ".", pipeName, PipeDirection.Out, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)); await client.ConnectAsync(timeout.Token); var bytes = Encoding.UTF8.GetBytes(message); await client.WriteAsync(bytes, timeout.Token); await client.FlushAsync(timeout.Token); return true; } catch { return false; } } public void Dispose() { cancellationSource.Cancel(); try { listenerTask?.Wait(TimeSpan.FromSeconds(1)); } catch (AggregateException) { } cancellationSource.Dispose(); if (IsFirstInstance) { try { mutex.ReleaseMutex(); } catch (ApplicationException) { } } mutex.Dispose(); } }