Files
eveo/src/Eve-O-Preview/Program.cs

208 lines
8.4 KiB
C#

using System;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using EveOPreview.Configuration;
using EveOPreview.Configuration.Implementation;
using EveOPreview.Presenters;
using EveOPreview.Services;
using EveOPreview.View;
using MediatR;
namespace EveOPreview {
static class Program {
private const string MUTEX_PREFIX = "EVE-O-Preview_Instance_";
private const string INSTANCE_ID_ARG = "--instance-id=";
private static Mutex _singleInstanceMutex;
private static int _instanceId = 0;
/// <summary>The main entry point for the application.</summary>
[STAThread]
static void Main(string[] args) {
// Parse instance ID from command line arguments, or auto-detect next available
Program._instanceId = ParseInstanceId(args);
// If no instance ID was provided, find the next available one
if (Program._instanceId == 0 && !HasInstanceIdArgument(args)) {
Program._instanceId = FindNextAvailableInstanceId();
}
// The very usual Mutex-based single-instance screening
// 'token' variable is used to store reference to the instance Mutex
// during the app lifetime
Program._singleInstanceMutex = Program.GetInstanceToken(Program._instanceId);
// If it was not possible to acquire the app token then another app instance is already running
// Nothing to do here
if (Program._singleInstanceMutex == null) {
return;
}
ExceptionHandler handler = new ExceptionHandler();
handler.SetupExceptionHandlers();
IApplicationController controller = Program.InitializeApplicationController();
Program.InitializeWinForms();
controller.Run<MainFormPresenter>();
}
private static bool HasInstanceIdArgument(string[] args) {
if (args == null || args.Length == 0) {
return false;
}
return args.Any(a => a.StartsWith(INSTANCE_ID_ARG));
}
private static int ParseInstanceId(string[] args) {
if (args == null || args.Length == 0) {
return 0;
}
var arg = args.FirstOrDefault(a => a.StartsWith(INSTANCE_ID_ARG));
if (arg != null && int.TryParse(arg.Substring(INSTANCE_ID_ARG.Length), out int id)) {
return id;
}
return 0;
}
private static int FindNextAvailableInstanceId() {
// Try instance IDs starting from 0, find the first one that's not in use
for (int id = 0; id < 10; id++) {
string mutexName = MUTEX_PREFIX + id;
try {
Mutex.OpenExisting(mutexName);
// This instance ID is taken, try next
} catch {
// This instance ID is available
return id;
}
}
return 0; // Fallback to 0 if all are taken
}
private static Mutex GetInstanceToken(int instanceId) {
string mutexName = MUTEX_PREFIX + instanceId;
// The code might look overcomplicated here for a single Mutex operation
// Yet we had already experienced a Windows-level issue
// where .NET finalizer thread was literally paralyzed by
// a failed Mutex operation. That did lead to weird OutOfMemory
// exceptions later
try {
Mutex.OpenExisting(mutexName);
// if that didn't fail then another instance is already running
return null;
} catch (UnauthorizedAccessException) {
return null;
} catch (Exception) {
Mutex token = new Mutex(true, mutexName, out var result);
return result ? token : null;
}
}
private static void InitializeWinForms() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
#if WINDOWS
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
#endif
}
private static IApplicationController InitializeApplicationController() {
IIocContainer container = new LightInjectContainer();
// Singleton registration is used for services
// Low-level services
container.Register<IWindowManager>();
container.Register<IProcessMonitor>();
// MediatR
container.Register<IMediator, MediatR.Mediator>();
container.RegisterInstance<ServiceFactory>(t => container.Resolve(t));
container.Register(typeof(INotificationHandler<>), typeof(Program).Assembly);
container.Register(typeof(IRequestHandler<>), typeof(Program).Assembly);
container.Register(typeof(IRequestHandler<, >), typeof(Program).Assembly);
// Configuration services
container.Register<IAppConfig>();
container.Register<IThumbnailConfiguration>();
container.Register<IConfigurationStorage>();
container.Register<IProfileManager>();
// Initialize profile system
Program.InitializeProfileSystem(container);
// Application services
container.Register<IThumbnailManager>();
container.Register<IThumbnailViewFactory>();
container.Register<IThumbnailDescription>();
IApplicationController controller = new ApplicationController(container);
// UI classes
controller.RegisterView<StaticThumbnailView, StaticThumbnailView>();
controller.RegisterView<LiveThumbnailView, LiveThumbnailView>();
controller.RegisterView<IMainFormView, MainForm>();
controller.RegisterInstance(new ApplicationContext());
return controller;
}
private static void InitializeProfileSystem(IIocContainer container) {
var profileManager = container.Resolve<IProfileManager>();
var configurationStorage = container.Resolve<IConfigurationStorage>();
var thumbnailConfiguration = container.Resolve<IThumbnailConfiguration>();
// Get the active profile for this instance based on instance ID
string activeProfile = profileManager.GetProfileForInstance(Program._instanceId);
// Acquire the profile lock for this instance
profileManager.AcquireProfileLock(activeProfile);
// Set the current profile in the configuration storage
if (configurationStorage is ConfigurationStorage configStorage) {
configStorage.CurrentProfile = activeProfile;
}
// Update the thumbnail configuration with the active profile
thumbnailConfiguration.CurrentProfile = activeProfile;
thumbnailConfiguration.AvailableProfiles = profileManager.GetAvailableProfiles();
}
public static void RestartApplication() {
// Get the current executable path
string executablePath = System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName;
if (string.IsNullOrEmpty(executablePath)) {
return;
}
// Release the mutex first so the new instance can start
Program._singleInstanceMutex?.Dispose();
Program._singleInstanceMutex = null;
// Small delay to ensure the mutex is fully released
System.Threading.Thread.Sleep(100);
// Start a new instance of the application with the same instance ID
try {
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo {
FileName = executablePath,
Arguments = INSTANCE_ID_ARG + Program._instanceId,
UseShellExecute = true
});
} catch (Exception) {
// If starting the new process fails, we're in a bad state
// Just exit anyway
}
// Exit the current application immediately
Environment.Exit(0);
}
public static int InstanceId => Program._instanceId;
}
}