namespace Zcbot.WindowsNode; internal sealed class TrayApplicationContext : ApplicationContext { private readonly NodeConfigStore store; private readonly ConfigurationForm form; private readonly NotifyIcon tray; private readonly ToolStripMenuItem statusItem; private CancellationTokenSource? connectionStop; private Task? connectionTask; private NodeConfig? config; private NodeStatus current = NodeStatus.Create(NodeState.NotRegistered, "尚未注册"); internal TrayApplicationContext(NodeConfigStore store) { this.store = store; form = new ConfigurationForm(); form.RegisterRequested += RegisterAsync; form.ReconnectRequested += RestartConnection; form.ResetIdentityRequested += ResetIdentity; statusItem = new ToolStripMenuItem("尚未注册") { Enabled = false }; var menu = new ContextMenuStrip(); menu.Items.Add(statusItem); menu.Items.Add(new ToolStripSeparator()); menu.Items.Add("打开配置", null, (_, _) => ShowConfiguration()); menu.Items.Add("立即重连", null, (_, _) => RestartConnection()); menu.Items.Add(new ToolStripSeparator()); menu.Items.Add("退出", null, (_, _) => ExitNode()); tray = new NotifyIcon { ContextMenuStrip = menu, Icon = TrayIconFactory.Create(NodeState.NotRegistered), Text = "zcbot Windows Node - 尚未注册", Visible = true, }; tray.DoubleClick += (_, _) => ShowConfiguration(); if (store.Exists) { try { config = store.Load(); StartConnection(); } catch (NodeConfigurationException exception) { UpdateStatus(NodeStatus.Create(NodeState.AuthenticationRequired, exception.Message)); ShowConfiguration(); } } else { UpdateStatus(current); ShowConfiguration(); } } protected override void Dispose(bool disposing) { if (disposing) { connectionStop?.Cancel(); tray.Visible = false; tray.Dispose(); form.Dispose(); connectionStop?.Dispose(); } base.Dispose(disposing); } private async Task RegisterAsync(EnrollOptions options) { UpdateStatus(NodeStatus.Create(NodeState.Connecting, "正在注册节点…")); try { await EnrollmentClient.EnrollAsync(options, store, CancellationToken.None); config = store.Load(); UpdateStatus(NodeStatus.Create(NodeState.Connecting, "注册成功,正在连接…")); StartConnection(); } catch { UpdateStatus(NodeStatus.Create(NodeState.NotRegistered, "注册失败,请检查配置")); throw; } } private void StartConnection() { if (config is null || connectionTask is { IsCompleted: false }) { return; } connectionStop?.Dispose(); connectionStop = new CancellationTokenSource(); connectionTask = RunConnectionAsync(config, connectionStop.Token); } private async Task RunConnectionAsync(NodeConfig nodeConfig, CancellationToken cancellationToken) { try { await new NodeConnectionLoop(nodeConfig, status => PostStatus(status)) .RunAsync(cancellationToken); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { PostStatus(NodeStatus.Create(NodeState.Stopped, "连接已停止")); } catch (NodeConfigurationException exception) { PostStatus(NodeStatus.Create(NodeState.AuthenticationRequired, exception.Message)); } catch (Exception exception) { PostStatus(NodeStatus.Create(NodeState.Offline, $"节点异常:{exception.Message}")); } } private void RestartConnection() { if (config is null) { ShowConfiguration(); return; } connectionStop?.Cancel(); _ = RestartAfterStopAsync(); } private void ResetIdentity() { connectionStop?.Cancel(); store.DeleteLocalIdentity(); config = null; UpdateStatus(NodeStatus.Create(NodeState.NotRegistered, "本机身份已清除,请使用新注册码注册")); } private async Task RestartAfterStopAsync() { if (connectionTask is not null) { await connectionTask; } StartConnection(); } private void PostStatus(NodeStatus status) { if (form.IsHandleCreated) { form.BeginInvoke(() => UpdateStatus(status)); } } private void UpdateStatus(NodeStatus status) { current = status; form.ApplyStatus(status, config); statusItem.Text = status.Message; tray.Text = Limit($"zcbot Windows Node - {status.Message}", 63); var oldIcon = tray.Icon; tray.Icon = TrayIconFactory.Create(status.State); oldIcon?.Dispose(); } private void ShowConfiguration() { form.ApplyStatus(current, config); form.Show(); form.WindowState = FormWindowState.Normal; form.Activate(); } private void ExitNode() { connectionStop?.Cancel(); tray.Visible = false; ExitThread(); } private static string Limit(string value, int length) => value.Length <= length ? value : value[..length]; }