176 lines
5.6 KiB
C#
176 lines
5.6 KiB
C#
using System.Diagnostics;
|
|
using System.Drawing;
|
|
|
|
namespace Neta.Tray;
|
|
|
|
public sealed class TrayApplicationContext : ApplicationContext
|
|
{
|
|
private readonly NotifyIcon _notifyIcon;
|
|
private readonly BackendProcessManager _processManager;
|
|
private readonly StatusClient _statusClient;
|
|
private RuntimeBootstrapInfo? _runtime;
|
|
private RuntimeStatusDto? _lastStatus;
|
|
private bool _starting;
|
|
|
|
public TrayApplicationContext()
|
|
: this(new BackendProcessManager(), new StatusClient(new HttpClient()))
|
|
{
|
|
}
|
|
|
|
internal TrayApplicationContext(BackendProcessManager processManager, StatusClient statusClient)
|
|
{
|
|
_processManager = processManager;
|
|
_statusClient = statusClient;
|
|
_notifyIcon = new NotifyIcon
|
|
{
|
|
Text = "Neta - 启动中…",
|
|
Icon = SystemIcons.Application,
|
|
Visible = true,
|
|
ContextMenuStrip = new ContextMenuStrip()
|
|
};
|
|
|
|
_notifyIcon.ContextMenuStrip.Items.Add("打开系统", null, WrapAsync(OpenSystemAsync));
|
|
_notifyIcon.ContextMenuStrip.Items.Add("重启服务", null, WrapAsync(RestartBackendAsync));
|
|
_notifyIcon.ContextMenuStrip.Items.Add("停止服务", null, WrapAsync(StopBackendAsync));
|
|
_notifyIcon.ContextMenuStrip.Items.Add("打开日志目录", null, (_, _) => OpenLogs());
|
|
_notifyIcon.ContextMenuStrip.Items.Add("打开配置目录", null, (_, _) => OpenConfigDir());
|
|
_notifyIcon.ContextMenuStrip.Items.Add("退出程序", null, WrapAsync(ExitAllAsync));
|
|
|
|
_ = StartBackendSafe();
|
|
}
|
|
|
|
private EventHandler WrapAsync(Func<Task> action) => async (_, _) =>
|
|
{
|
|
try { await action(); }
|
|
catch (Exception ex) { ShowError(ex.Message); }
|
|
};
|
|
|
|
private void ShowError(string message)
|
|
{
|
|
_notifyIcon.BalloonTipTitle = "Neta";
|
|
_notifyIcon.BalloonTipText = message;
|
|
_notifyIcon.BalloonTipIcon = ToolTipIcon.Error;
|
|
_notifyIcon.ShowBalloonTip(5000);
|
|
}
|
|
|
|
private void MarkRunning()
|
|
{
|
|
_notifyIcon.Text = "Neta - 运行中";
|
|
}
|
|
|
|
private async Task StartBackendSafe()
|
|
{
|
|
try
|
|
{
|
|
await EnsureBackendAttachedAsync();
|
|
MarkRunning();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_notifyIcon.Text = "Neta - 启动失败";
|
|
ShowError($"后端启动失败: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task EnsureBackendAttachedAsync()
|
|
{
|
|
if (_starting) return;
|
|
_starting = true;
|
|
try
|
|
{
|
|
_runtime = RuntimeInfoStore.LoadFromInstalledConfig(AppContext.BaseDirectory);
|
|
|
|
if (_runtime is not null)
|
|
{
|
|
var status = await _statusClient.GetStatusAsync(_runtime, CancellationToken.None);
|
|
if (status is { Ready: true })
|
|
{
|
|
_lastStatus = status;
|
|
MarkRunning();
|
|
return;
|
|
}
|
|
|
|
if (_processManager.IsBackendProcessAlive(_runtime.Pid))
|
|
return;
|
|
}
|
|
|
|
var traySecret = Guid.NewGuid().ToString("N");
|
|
var backendExe = Path.Combine(AppContext.BaseDirectory, "backend.exe");
|
|
_processManager.Start(backendExe, traySecret);
|
|
_runtime = RuntimeInfoStore.WaitUntilAvailable(AppContext.BaseDirectory, TimeSpan.FromSeconds(30));
|
|
_lastStatus = await _statusClient.GetStatusAsync(_runtime, CancellationToken.None);
|
|
if (_lastStatus is not null)
|
|
{
|
|
MarkRunning();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_starting = false;
|
|
}
|
|
}
|
|
|
|
private async Task OpenSystemAsync()
|
|
{
|
|
if (_lastStatus is null) await EnsureBackendAttachedAsync();
|
|
if (_lastStatus is null) throw new InvalidOperationException("后端尚未就绪");
|
|
MarkRunning();
|
|
Process.Start(new ProcessStartInfo(_lastStatus.Url) { UseShellExecute = true });
|
|
}
|
|
|
|
private async Task RestartBackendAsync()
|
|
{
|
|
await StopBackendAsync();
|
|
_lastStatus = null;
|
|
_notifyIcon.Text = "Neta - 重启中…";
|
|
await EnsureBackendAttachedAsync();
|
|
_notifyIcon.Text = "Neta - 运行中";
|
|
}
|
|
|
|
private async Task StopBackendAsync()
|
|
{
|
|
var backendExePath = Path.Combine(AppContext.BaseDirectory, "backend.exe");
|
|
_runtime ??= RuntimeInfoStore.LoadFromInstalledConfig(AppContext.BaseDirectory);
|
|
if (_runtime is not null)
|
|
{
|
|
var pid = _runtime.Pid;
|
|
var stoppedGracefully = false;
|
|
try
|
|
{
|
|
await _statusClient.StopAsync(_runtime, CancellationToken.None);
|
|
stoppedGracefully = true;
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
_processManager.WaitForExit(pid, TimeSpan.FromSeconds(10));
|
|
if (_processManager.IsBackendProcessAlive(pid))
|
|
{
|
|
_processManager.KillProcess(pid);
|
|
}
|
|
_notifyIcon.Text = stoppedGracefully ? "Neta - 已停止" : "Neta - 已强制停止";
|
|
}
|
|
else
|
|
{
|
|
_notifyIcon.Text = "Neta - 已强制停止";
|
|
}
|
|
|
|
_processManager.KillInstalledBackendProcesses(backendExePath);
|
|
_lastStatus = null;
|
|
_runtime = null;
|
|
}
|
|
|
|
private async Task ExitAllAsync()
|
|
{
|
|
await StopBackendAsync();
|
|
_notifyIcon.Visible = false;
|
|
_notifyIcon.Dispose();
|
|
ExitThread();
|
|
}
|
|
|
|
private void OpenLogs() => RuntimeInfoStore.OpenLogs(AppContext.BaseDirectory);
|
|
|
|
private void OpenConfigDir() => RuntimeInfoStore.OpenConfigDir(AppContext.BaseDirectory);
|
|
}
|