From f8b03479f096ea6e6219ec3ee692fe6ac6ba00f2 Mon Sep 17 00:00:00 2001 From: huangxiuqi <1417617276@qq.com> Date: Mon, 29 Jan 2024 23:39:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NectarRCON/App.xaml.cs | 1 + NectarRCON/Interfaces/IHistoryService.cs | 33 +++++ NectarRCON/Services/HistoryService.cs | 155 +++++++++++++++++++++ NectarRCON/ViewModels/MainPageViewModel.cs | 31 ++++- NectarRCON/Views/Pages/MainPage.xaml | 2 +- 5 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 NectarRCON/Interfaces/IHistoryService.cs create mode 100644 NectarRCON/Services/HistoryService.cs diff --git a/NectarRCON/App.xaml.cs b/NectarRCON/App.xaml.cs index 22e1879..64d0d7c 100644 --- a/NectarRCON/App.xaml.cs +++ b/NectarRCON/App.xaml.cs @@ -47,6 +47,7 @@ public partial class App services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/NectarRCON/Interfaces/IHistoryService.cs b/NectarRCON/Interfaces/IHistoryService.cs new file mode 100644 index 0000000..e52a3e4 --- /dev/null +++ b/NectarRCON/Interfaces/IHistoryService.cs @@ -0,0 +1,33 @@ +namespace NectarRCON.Interfaces; + +public class HistoryNode +{ + public string? Cmd { get; set; } + + public HistoryNode? Prev { get; set; } + + public HistoryNode? Next { get; set; } +} + +public interface IHistoryService +{ + /// + /// 获取前一条命令 + /// + /// + /// + HistoryNode? Prev(HistoryNode? current); + + /// + /// 获取后一条命令 + /// + /// + /// + HistoryNode? Next(HistoryNode? current); + + /// + /// 输入命令 + /// + /// + HistoryNode? InputCmd(string cmd); +} diff --git a/NectarRCON/Services/HistoryService.cs b/NectarRCON/Services/HistoryService.cs new file mode 100644 index 0000000..b11bb65 --- /dev/null +++ b/NectarRCON/Services/HistoryService.cs @@ -0,0 +1,155 @@ +using NectarRCON.Interfaces; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace NectarRCON.Services; + +public class HistoryService : IHistoryService +{ + + /// + /// 双向链表保存历史记录 + /// + private HistoryNode? _head; + private HistoryNode? _tail; + + private readonly Dictionary _map; + + /// + /// 限制记录条数 + /// + private int _limit = 20; + + public HistoryService() + { + _map = []; + if (!Path.Exists("./logs")) + { + Directory.CreateDirectory("./logs"); + } + + var stream = File.Open($"./logs/history", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); + using StreamReader reader = new StreamReader(stream); + string? line; + while ((line = reader.ReadLine()) != null) + { + line = line.Trim(); + if (!string.IsNullOrEmpty(line)) + { + AppendNode(line); + } + } + } + + public HistoryNode? InputCmd(string cmd) + { + var node = AppendNode(cmd); + Save(); + return node; + } + + private HistoryNode? AppendNode(string cmd) + { + cmd = cmd.Trim(); + HistoryNode node; + if (_map.ContainsKey(cmd)) + { + // 已存在的记录,将其移动到链表尾部 + node = _map[cmd]; + if (node == _tail) + { + return null; + } + else if (node == _head) + { + _head = _head.Next; + } + + if (node.Prev != null) + { + node.Prev.Next = node.Next; + } + if (node.Next != null) + { + node.Next.Prev = node.Prev; + } + node.Prev = null; + node.Next = null; + } + else + { + node = new HistoryNode() + { + Cmd = cmd, + }; + _map.Add(cmd, node); + } + + if (_head == null || _tail == null) + { + _head = _tail = node; + } + else + { + _tail.Next = node; + node.Prev = _tail; + _tail = node; + } + + // 超出限制,移除链表头部记录 + if (_map.Count() > _limit) + { + var head = _head; + _head = _head.Next; + if (_head != null) + { + _head.Prev = null; + } + if (!string.IsNullOrEmpty(head.Cmd)) + { + _map.Remove(head.Cmd); + } + } + + return null; + } + + public HistoryNode? Next(HistoryNode? current) + { + if (current == null) + { + return null; + } + return current.Next; + } + + public HistoryNode? Prev(HistoryNode? current) + { + if (current == null) + { + return _tail; + } + else if (current.Prev == null) + { + return current; + } + return current.Prev; + } + + private void Save() + { + using var stream = File.Open($"./logs/history", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); + stream.Seek(0, SeekOrigin.Begin); + HistoryNode? node = _head; + while (node != null) { + if (!string.IsNullOrEmpty(node.Cmd)) + { + string cmd = node.Cmd + "\n"; + stream.Write(Encoding.UTF8.GetBytes(cmd)); + } + node = node.Next; + } + } +} diff --git a/NectarRCON/ViewModels/MainPageViewModel.cs b/NectarRCON/ViewModels/MainPageViewModel.cs index 8344764..5e537c3 100644 --- a/NectarRCON/ViewModels/MainPageViewModel.cs +++ b/NectarRCON/ViewModels/MainPageViewModel.cs @@ -24,6 +24,7 @@ public partial class MainPageViewModel : ObservableObject { private static readonly RconSettingsDp RconSettings = DpFile.LoadSingleton(); private readonly ILogService _logService; + private readonly IHistoryService _historyService; private readonly IServerPasswordService _serverPasswordService; private IRconConnection _rconConnectService; private readonly INavigationService _navigationService; @@ -34,6 +35,7 @@ public partial class MainPageViewModel : ObservableObject private MainPage? _page; private TextBox? _logTextBox; + private HistoryNode? _historyNode; [ObservableProperty] private string _commandText = string.Empty; [ObservableProperty] private string _logText = string.Empty; @@ -43,6 +45,7 @@ public partial class MainPageViewModel : ObservableObject public MainPageViewModel() { _logService = App.GetService(); + _historyService = App.GetService(); _serverPasswordService = App.GetService(); _navigationService = App.GetService(); _languageService = App.GetService(); @@ -87,7 +90,7 @@ private async void Load(RoutedEventArgs e) // GetLogs LogText = string.Empty; LogText = _logService.GetText(); - + _page = e.Source as MainPage; await ConnectAsync(); } @@ -105,7 +108,7 @@ private async void ReConnect() private async Task ConnectAsync() { Log.Information($"[ConnectAsync] 准备连接到服务器"); - + IsMultipleConnection = _rconConnectionInfoService.HasMultipleInformation; _rconConnectService.OnConnected -= OnConnected; _rconConnectService.OnMessage -= OnMessage; @@ -123,7 +126,7 @@ private async Task ConnectAsync() { IsLoaded = true, }); - + Log.Information($"[ConnectAsync] 连接服务: {_rconConnectService.GetType().FullName}, 是否为多连接: {IsMultipleConnection}"); _logTextBox = (TextBox)LogicalTreeHelper.FindLogicalNode(_page, "LogText"); @@ -151,7 +154,7 @@ private async Task ConnectAsync() catch (Exception ex) { Log.Error($"[ConnectAsync] 连接遇到错误: {ex}"); - + var msg = _languageService.GetKey("text.server.connect.fail.text") .Replace("\\n", "\n") .Replace("%s", ex.Message); @@ -234,10 +237,28 @@ private void Run() private void KeyDown(KeyEventArgs e) { var textBox = (System.Windows.Controls.TextBox)e.Source; - _commandText = textBox.Text; if (e.Key == Key.Enter) { + var text = textBox.Text.Trim(); + if (string.IsNullOrEmpty(text)) + { + return; + } + _commandText = text; + _historyNode = _historyService.InputCmd(_commandText); Run(); } + else if (e.Key == Key.Up) + { + _historyNode = _historyService.Prev(_historyNode); + textBox.Text = _historyNode?.Cmd; + textBox.Select(textBox.Text?.Length ?? 0, 0); + } + else if (e.Key == Key.Down) + { + _historyNode = _historyService.Next(_historyNode); + textBox.Text = _historyNode?.Cmd; + textBox.Select(textBox.Text?.Length ?? 0, 0); + } } } \ No newline at end of file diff --git a/NectarRCON/Views/Pages/MainPage.xaml b/NectarRCON/Views/Pages/MainPage.xaml index 5d16158..52a463a 100644 --- a/NectarRCON/Views/Pages/MainPage.xaml +++ b/NectarRCON/Views/Pages/MainPage.xaml @@ -82,7 +82,7 @@ PlaceholderText="/"> + EventName="PreviewKeyDown">