InvoiceParser/App.xaml.cs
2026-09-16 13:09:19 -07:00

102 lines
3.7 KiB
C#

using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows;
using InvoiceParser.Src;
namespace InvoiceParser;
public partial class App : Application
{
private WebHost? _host;
private Mutex? _singleInstance;
protected override async void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// Nothing else catches these, and a silent crash on an unattended
// machine is exactly what the log exists to explain.
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
Log.Error("crash", "unhandled exception", args.ExceptionObject as Exception);
DispatcherUnhandledException += (_, args) =>
Log.Error("crash", "unhandled UI exception", args.Exception);
TaskScheduler.UnobservedTaskException += (_, args) =>
{
Log.Error("crash", "unobserved task exception", args.Exception);
args.SetObserved();
};
_singleInstance = new Mutex(true, "InvoiceParserApp_SingleInstance", out bool isNew);
if (!isNew)
{
// Bring the running instance forward instead of just saying it exists.
FocusRunningInstance();
Shutdown();
return;
}
try
{
var config = AppConfig.Load(AppContext.BaseDirectory);
_host = new WebHost(config);
await _host.StartAsync();
var win = new MainWindow(_host.BaseUrl);
MainWindow = win;
win.Show();
}
catch (Exception ex)
{
Log.Error("startup", "startup failed", ex);
MessageBox.Show("Startup failed: " + ex.Message, "Invoice Parser",
MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown();
}
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private static void FocusRunningInstance()
{
const int SW_RESTORE = 9;
var me = Process.GetCurrentProcess();
foreach (var p in Process.GetProcessesByName(me.ProcessName))
{
if (p.Id == me.Id || p.MainWindowHandle == IntPtr.Zero) continue;
ShowWindow(p.MainWindowHandle, SW_RESTORE);
SetForegroundWindow(p.MainWindowHandle);
return;
}
MessageBox.Show("Invoice Parser is already running.", "Invoice Parser",
MessageBoxButton.OK, MessageBoxImage.Information);
}
protected override void OnExit(ExitEventArgs e)
{
// Stop the host off the UI thread: awaiting it here can deadlock on the
// WPF SynchronizationContext, leaving a windowless process holding the
// single-instance mutex.
try
{
var host = _host;
if (host != null)
Task.Run(() => host.StopAsync()).Wait(TimeSpan.FromSeconds(5));
}
catch (Exception ex) { Log.Error("shutdown", "web host stop failed", ex); }
Log.Info("shutdown", "exiting with code " + e.ApplicationExitCode);
_singleInstance?.Dispose();
base.OnExit(e);
// Hard terminate. Environment.Exit runs finalizers and COM cleanup, which
// hangs on WebView2's browser processes and leaves a windowless process
// that holds the single-instance mutex and locks the exe against rebuilds.
// Safe here: every state.json write is synchronous and already flushed,
// and a parse abandoned mid-call is reset from "processing" to "queued"
// by ParseWorker on the next start.
Process.GetCurrentProcess().Kill();
}
}