Files
LanMountainDesktop/LanMountainDesktop/Program.cs

179 lines
6.2 KiB
C#
Raw Normal View History

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.WebView.Desktop;
2026-03-13 09:10:00 +08:00
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
2026-03-13 09:10:00 +08:00
using LanMountainDesktop.Services.Settings;
2026-02-26 23:08:19 +08:00
2026-03-04 15:22:52 +08:00
namespace LanMountainDesktop;
2026-02-26 23:08:19 +08:00
sealed class Program
{
2026-03-13 22:20:12 +08:00
internal static string StartupRenderMode { get; private set; } = AppRenderingModeHelper.Default;
2026-02-26 23:08:19 +08:00
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
2026-03-10 21:25:47 +08:00
public static void Main(string[] args)
{
AppLogger.Initialize();
RegisterGlobalExceptionLogging();
var restartParentProcessId = AppRestartService.TryGetRestartParentProcessId(args);
2026-03-10 21:25:47 +08:00
using var singleInstance = AcquireSingleInstance(restartParentProcessId);
if (!singleInstance.IsPrimaryInstance)
{
if (restartParentProcessId is not null)
{
AppLogger.Warn(
"Startup",
$"Restart relaunch could not acquire the single-instance lock. pid={restartParentProcessId.Value}. Suppressing multi-open activation prompt.");
return;
}
AppLogger.Warn("Startup", "A secondary launch was blocked because another instance is already running.");
_ = singleInstance.TryNotifyPrimaryInstance(TimeSpan.FromSeconds(2));
return;
}
2026-03-10 21:25:47 +08:00
var diagnostics = StartupDiagnosticsService.Run(args);
StartupDiagnosticsService.ShowLegacyExecutableWarningIfNeeded(diagnostics);
try
{
var renderMode = LoadConfiguredRenderMode();
2026-03-13 22:20:12 +08:00
StartupRenderMode = renderMode;
2026-03-10 21:25:47 +08:00
AppLogger.Info("Startup", $"Resolved render mode '{renderMode}'.");
App.CurrentSingleInstanceService = singleInstance;
2026-03-10 21:25:47 +08:00
BuildAvaloniaApp(renderMode).StartWithClassicDesktopLifetime(args);
AppLogger.Info("Startup", "Application exited normally.");
}
catch (Exception ex)
{
AppLogger.Critical("Startup", "Application terminated during startup.", ex);
throw;
}
finally
{
App.CurrentSingleInstanceService = null;
}
2026-03-10 21:25:47 +08:00
}
2026-02-26 23:08:19 +08:00
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp(string renderMode = AppRenderingModeHelper.Default)
{
var builder = AppBuilder.Configure<App>()
2026-02-26 23:08:19 +08:00
.UsePlatformDetect()
.UseDesktopWebView()
2026-02-26 23:08:19 +08:00
.WithInterFont()
.LogToTrace();
if (OperatingSystem.IsWindows())
{
var configuredModes = AppRenderingModeHelper.GetWin32RenderingModes(renderMode);
if (configuredModes is { Length: > 0 })
{
builder = builder.With(new Win32PlatformOptions
{
RenderingMode = configuredModes
});
}
}
return builder;
}
private static SingleInstanceService AcquireSingleInstance(int? restartParentProcessId)
{
var singleInstance = SingleInstanceService.CreateDefault();
if (singleInstance.IsPrimaryInstance || restartParentProcessId is null)
{
return singleInstance;
}
AppLogger.Info(
"Startup",
$"Restart relaunch detected. Waiting for previous instance pid={restartParentProcessId.Value} to exit before re-acquiring the single-instance lock.");
singleInstance.Dispose();
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(12);
WaitForRestartParentExit(restartParentProcessId.Value, deadline);
while (DateTime.UtcNow < deadline)
{
var retryInstance = SingleInstanceService.CreateDefault();
if (retryInstance.IsPrimaryInstance)
{
AppLogger.Info("Startup", "Restart relaunch acquired the single-instance lock.");
return retryInstance;
}
retryInstance.Dispose();
Thread.Sleep(150);
}
AppLogger.Warn(
"Startup",
$"Restart relaunch timed out while waiting for the single-instance lock. pid={restartParentProcessId.Value}.");
return SingleInstanceService.CreateDefault();
}
private static string LoadConfiguredRenderMode()
{
try
{
2026-03-13 09:10:00 +08:00
var snapshot = HostSettingsFacadeProvider.GetOrCreate()
.Settings
.LoadSnapshot<AppSettingsSnapshot>(LanMountainDesktop.PluginSdk.SettingsScope.App);
return AppRenderingModeHelper.Normalize(snapshot.AppRenderMode);
}
2026-03-10 21:25:47 +08:00
catch (Exception ex)
{
2026-03-10 21:25:47 +08:00
AppLogger.Warn("Startup", "Failed to load configured render mode. Falling back to default.", ex);
return AppRenderingModeHelper.Default;
}
}
2026-03-10 21:25:47 +08:00
private static void WaitForRestartParentExit(int processId, DateTime deadlineUtc)
{
try
{
using var process = Process.GetProcessById(processId);
var remaining = deadlineUtc - DateTime.UtcNow;
if (remaining > TimeSpan.Zero)
{
process.WaitForExit((int)Math.Ceiling(remaining.TotalMilliseconds));
}
}
catch (ArgumentException)
{
// The previous process already exited before we started waiting.
}
catch (Exception ex)
{
AppLogger.Warn("Startup", $"Failed while waiting for restart parent pid={processId} to exit.", ex);
}
}
2026-03-10 21:25:47 +08:00
private static void RegisterGlobalExceptionLogging()
{
AppDomain.CurrentDomain.UnhandledException += (_, eventArgs) =>
{
AppLogger.Critical(
"UnhandledException",
$"Unhandled exception. IsTerminating={eventArgs.IsTerminating}",
eventArgs.ExceptionObject as Exception);
};
TaskScheduler.UnobservedTaskException += (_, eventArgs) =>
{
AppLogger.Error("TaskScheduler", "Unobserved task exception.", eventArgs.Exception);
eventArgs.SetObserved();
2026-03-10 21:25:47 +08:00
};
}
2026-02-26 23:08:19 +08:00
}