208 lines
8.4 KiB
C#
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;
|
|
}
|
|
} |