Mesen2/UI/Utilities/SingleInstance.cs

152 lines
No EOL
3.8 KiB
C#

using System;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Collections.Generic;
using System.Threading.Tasks;
using Mesen.Config;
using System.Runtime.CompilerServices;
namespace Mesen.Utilities
{
public class SingleInstance : IDisposable
{
public static SingleInstance Instance { get; private set; } = new SingleInstance();
private static Guid _identifier = new Guid("{A46696B7-2D1C-4CC5-A52F-43BCAF094AEF}");
private Mutex? _mutex;
private FileStream? _lockFileStream;
private bool _disposed = false;
private bool _firstInstance = false;
public bool FirstInstance => _firstInstance || !ConfigManager.Config.Preferences.SingleInstance;
public event EventHandler<ArgumentsReceivedEventArgs>? ArgumentsReceived;
public void Init(string[] args)
{
if(OperatingSystem.IsLinux() && !RuntimeFeature.IsDynamicCodeSupported) {
//Linux NativeAOT doesn't appear to work correctly here, use file lock instead
try {
_lockFileStream = File.Open(Path.Combine(ConfigManager.HomeFolder, "mesen.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
_lockFileStream.Lock(0, 0);
_firstInstance = true;
} catch {
_firstInstance = false;
}
} else {
_mutex = new Mutex(true, "Global\\" + _identifier.ToString(), out _firstInstance);
}
if(_firstInstance || !ConfigManager.Config.Preferences.SingleInstance) {
Task.Run(() => ListenForArguments());
} else {
PassArgumentsToFirstInstance(args);
}
}
private bool PassArgumentsToFirstInstance(string[] args)
{
try {
using(NamedPipeClientStream client = new NamedPipeClientStream(_identifier.ToString())) {
using(StreamWriter writer = new StreamWriter(client)) {
client.Connect(200);
foreach(string argument in args) {
string absPath;
if(Path.IsPathRooted(argument)) {
absPath = argument;
} else {
absPath = Path.GetFullPath(argument, Program.OriginalFolder);
}
if(File.Exists(absPath)) {
//Send absolute path to the other instance, to ensure it can find
//the file even though its startup folder might have been different
writer.WriteLine(absPath);
} else {
writer.WriteLine(argument);
}
}
}
}
return true;
} catch(Exception ex) {
Console.WriteLine("Error: " + ex.ToString());
}
return false;
}
private void ListenForArguments()
{
try {
while(true) {
using NamedPipeServerStream server = new NamedPipeServerStream(_identifier.ToString());
using StreamReader reader = new StreamReader(server);
server.WaitForConnection();
List<string> args = new List<string>();
while(server.IsConnected) {
string? arg = reader.ReadLine();
if(!string.IsNullOrWhiteSpace(arg)) {
args.Add(arg);
} else {
server.Disconnect();
break;
}
}
Task.Run(() => {
ArgumentsReceived?.Invoke(this, new ArgumentsReceivedEventArgs(args.ToArray()));
});
}
} catch(Exception ex) {
Console.WriteLine("ListenForArguments error: " + ex.ToString());
} finally {
Thread.Sleep(10000);
Task.Run(() => ListenForArguments());
}
}
#region IDisposable
protected virtual void Dispose(bool disposing)
{
if(!_disposed) {
if(_mutex != null) {
if(_firstInstance) {
_mutex.ReleaseMutex();
}
_mutex.Dispose();
_mutex = null;
}
_lockFileStream = null;
_disposed = true;
}
}
~SingleInstance()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
public class ArgumentsReceivedEventArgs : EventArgs
{
public string[] Args { get; }
public ArgumentsReceivedEventArgs(string[] args)
{
Args = args;
}
}
}